"""Belegverarbeitung für Shell-Tankbelege (§7.7, §10 Punkt 7).

Beleg-Upload direkt in der App statt Postfachanbindung - kein IMAP-Zugriff.

shell_beleg_parser.py, im Lastenheft (§2) als mitgeliefert genannt, lag
beim Bau nicht im Projektordner vor (siehe Ersteinschätzung) und wurde
nachträglich unter audi_dashboard/shell_beleg_parser.py gegen einen echten
Shell-eReceipt neu geschrieben (siehe dessen Kopfkommentar). Aufruf über
eine Kommandozeilen-Schnittstelle: `python3 shell_beleg_parser.py
<pfad-zur-pdf>`, Ausgabe als JSON auf stdout mit den Feldern aus §6.4
(receipt_key, ts, station_name, liters, fuel_total_eur, discount, ...).
Muss unter /config/audi_dashboard/shell_beleg_parser.py liegen (siehe
INSTALL.md) - eigener Prozess mit dem System-python3 des Containers, nicht
die pyscript-Sandbox, deshalb dort einmalig `pip install pypdf` nötig.

ABWEICHUNG VOM URSPRÜNGLICHEN LASTENHEFT (Frontend-Anpassung, nach dem
ursprünglichen Bau nachträglich verlangt):
- Volltankung (§7.4) ist entfernt - weder Berechnung noch Anzeige. Damit
  entfallen auch level_before_pct/level_after_pct, die nur dafür gebraucht
  wurden.
- fuel_total_eur ist immer der reale, bereits rabattierte Betrag.
  Die SmartDeal-Ersparnis steht separat in discount. price_per_l wird
  grundsätzlich aus fuel_total_eur ÷ liters berechnet, nie aus einem vom
  Beleg übernommenen Wert übernommen - siehe Kopfkommentar in
  audi-dashboard-app.js (tankFelder()) für dieselbe Regel im Frontend.

Entity-IDs stehen zentral in pyscript/modules/einstellungen.py.
"""

import base64
import datetime
import io
import json
import os
import subprocess

import einstellungen
import frontend_veroeffentlichung
import profil

PARSER_SKRIPT = "/config/audi_dashboard/shell_beleg_parser.py"


# --------------------------------------------------------------- Hilfsfunktionen

def _pdf_speichern(inhalt_bytes, dateiname):
    # io.open über task.executor, kein with, kein bloßes open() - siehe
    # Kopfkommentar in profil.py für alle drei Gründe.
    os.makedirs(profil.BELEGE_ORDNER, exist_ok=True)
    pfad = f"{profil.BELEGE_ORDNER}/{dateiname}"
    f = task.executor(io.open, pfad, "wb")
    f.write(inhalt_bytes)
    f.close()
    return pfad


def _parser_aufrufen(pdf_pfad):
    ergebnis = subprocess.run(
        ["python3", PARSER_SKRIPT, pdf_pfad],
        capture_output=True, text=True, timeout=30,
    )
    if ergebnis.returncode != 0:
        raise RuntimeError(f"shell_beleg_parser.py fehlgeschlagen: {ergebnis.stderr}")
    return json.loads(ergebnis.stdout)


def _preis_je_liter(fuel_total_eur, liters):
    if not liters:
        return None
    return round(fuel_total_eur / liters, 3)


# -------------------------------------------------------------------- Service

@service
def audi_dashboard_beleg_hochladen(pdf_base64=None, dateiname=None, tank_id=None):
    """Nimmt einen per Base64 codierten PDF-Beleg entgegen (Upload aus der
    Oberfläche über hass.callService).

    Mit tank_id: trägt die Belegdaten direkt in einen bereits bestehenden
    Tankvorgang nach - egal ob der ursprünglich manuell oder automatisch
    angelegt wurde. Der Kilometerstand/die gefahrene Distanz dieses
    Tankvorgangs bleiben dabei unangetastet (die kommen weiterhin vom
    Fahrzeug, nie vom Beleg, §7.7 Regel 2), nur die Beleg-Felder (Liter,
    Kosten, Ersparnis, Station, Zeitstempel, ...) werden ergänzt
    beziehungsweise überschrieben. Passt das Belegdatum nicht zum bereits
    erfassten Tankvorgang (unterschiedlicher Kalendertag), wird nichts
    übernommen - stattdessen ein Fehler veröffentlicht, damit in der
    Oberfläche nicht versehentlich ein falscher Beleg einem bestehenden
    Eintrag zugeordnet wird.

    Ohne tank_id: legt KEINEN eigenständigen Tankvorgang mehr an (frühere
    Abweichung vom Lastenheft, auf Wunsch entfernt), sondern parst den Beleg
    nur und veröffentlicht das Ergebnis - die Oberfläche übernimmt die Werte
    in den gerade offenen Entwurf (neuer oder bearbeiteter Tankvorgang) und
    speichert erst auf einen expliziten Klick auf "Speichern".

    Aufruf als pyscript.audi_dashboard_beleg_hochladen."""
    if not pdf_base64:
        log.warning("audi_dashboard: kein pdf_base64 übergeben")
        return

    # task.executor entfällt hier bewusst: _pdf_speichern/_parser_aufrufen sind
    # in dieser pyscript-Datei definiert, task.executor akzeptiert aber nur
    # echte externe Python-Funktionen (siehe Kopfkommentar in profil.py).
    inhalt_bytes = base64.b64decode(pdf_base64)
    dateiname = dateiname or f"beleg_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
    pdf_pfad = _pdf_speichern(inhalt_bytes, dateiname)

    try:
        geparst = _parser_aufrufen(pdf_pfad)
    except Exception as exc:
        log.error(f"audi_dashboard: Beleg konnte nicht geparst werden: {exc}")
        frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({"fehler": "Beleg konnte nicht gelesen werden", "tank_id": tank_id})
        return

    receipt_key = geparst.get("receipt_key")
    if not receipt_key:
        log.error("audi_dashboard: Parser lieferte keinen receipt_key")
        frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({"fehler": "Beleg konnte nicht gelesen werden", "tank_id": tank_id})
        return

    # §7.7 Regel 3: derselbe Beleg (receipt_key, minutengenau) erzeugt
    # keinen zweiten Datensatz
    vorhandener_beleg = profil.tankvorgang_nach_receipt_key(receipt_key)
    if vorhandener_beleg is not None and vorhandener_beleg.get("tank_id") != tank_id:
        log.info(f"audi_dashboard: Beleg {receipt_key} bereits vorhanden, übersprungen")
        frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({"fehler": "Beleg bereits erfasst", "tank_id": tank_id})
        return

    liters = geparst.get("liters")
    fuel_total_eur = geparst.get("fuel_total_eur")  # real, bereits rabattiert

    beleg_felder = {
        "receipt_key": receipt_key,
        "receipt_no": geparst.get("receipt_no"),
        "tse_beleg_nr": geparst.get("tse_beleg_nr"),
        "ts": geparst.get("ts"),
        "ts_payment": geparst.get("ts_payment"),
        "ts_tse": geparst.get("ts_tse"),
        "station_id": geparst.get("station_id"),
        "station_name": geparst.get("station_name"),
        "station_address": geparst.get("station_address"),
        "article_no": geparst.get("article_no"),
        "product_name": geparst.get("product_name"),
        "fuel_type": geparst.get("fuel_type"),
        # Liter, Kosten und Ersparnis kommen vom Beleg (§7.7 Regel 2).
        # price_per_l wird nie vom Beleg übernommen, sondern immer berechnet.
        "liters": liters,
        "fuel_total_eur": fuel_total_eur,
        "price_per_l": _preis_je_liter(fuel_total_eur, liters),
        "discount": geparst.get("discount"),  # SmartDeal-Ersparnis
        "discount_per_l": geparst.get("discount_per_l"),
        "list_price_per_l": geparst.get("list_price_per_l"),
        "receipt_total_eur": geparst.get("receipt_total_eur"),
        "net_eur": geparst.get("net_eur"),
        "vat_eur": geparst.get("vat_eur"),
        "receipt_file": pdf_pfad,
    }

    if tank_id:
        vorhanden = profil.tankvorgang_nach_id(tank_id)
        if not vorhanden:
            log.warning(f"audi_dashboard: Tankvorgang {tank_id} für Beleg-Nachtrag nicht gefunden")
            frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({"fehler": "Tankvorgang nicht gefunden", "tank_id": tank_id})
            return
        vorhandenes_datum = (vorhanden.get("ts") or "")[:10]
        belegdatum = (geparst.get("ts") or "")[:10]
        if vorhandenes_datum and belegdatum and vorhandenes_datum != belegdatum:
            log.warning(f"audi_dashboard: Belegdatum {belegdatum} passt nicht zu Tankvorgang {tank_id} (Datum {vorhandenes_datum})")
            frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({"fehler": "Falsches Belegdatum", "tank_id": tank_id})
            return
        aenderungen = dict(beleg_felder)
        aenderungen["status"] = "vollständig"
        if profil.tankvorgang_aktualisieren(tank_id, aenderungen):
            frontend_veroeffentlichung.tankvorgaenge_veroeffentlichen()
            frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({"erfolg": True, "tank_id": tank_id})
            log.info(f"audi_dashboard: Beleg {receipt_key} in Tankvorgang {tank_id} nachgetragen")
        else:
            frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({"fehler": "Tankvorgang nicht gefunden", "tank_id": tank_id})
        return

    # Kein tank_id: nur parsen und dem gerade offenen Entwurf in der
    # Oberfläche zum Ausfüllen bereitstellen - legt selbst KEINEN
    # Tankvorgang an (siehe Kopfkommentar oben).
    frontend_veroeffentlichung.beleg_ergebnis_veroeffentlichen({**beleg_felder, "tank_id": None})
    log.info(f"audi_dashboard: Beleg {receipt_key} geparst, wartet auf Speichern in der Oberfläche")


@service
def audi_dashboard_tankvorgang_manuell(ts=None, liter=None, kosten=None, km=None, ersparnis=None, station=None, distanz=None, kraftstoff=None, receipt_key=None, receipt_file=None):
    """Erfassung eines Tankvorgangs aus dem Formular "Neuer Tankvorgang" -
    Fallback aus §7.7 / §10 Punkt 7, unter „Tanken" vorgesehen (§5.5).
    Dieselben Felder wie beim Bearbeiten eines bestehenden Vorgangs (siehe
    audi_dashboard_tankvorgang_aktualisieren). receipt_key/receipt_file sind
    optional: kommen sie mit (weil zuvor ein Beleg über
    audi_dashboard_beleg_hochladen ohne tank_id in den Entwurf übernommen
    wurde), zählt der Tankvorgang als per Beleg erfasst, sonst als manuell.
    Aufruf als pyscript.audi_dashboard_tankvorgang_manuell."""
    if liter is None or kosten is None:
        log.warning("audi_dashboard: liter und kosten sind Pflichtfelder")
        return

    odometer_km = km if km is not None else frontend_veroeffentlichung.zustand_oder_none(einstellungen.KM_SENSOR)
    tankvorgang = {
        "tank_id": profil.neue_id("f"),
        "receipt_key": receipt_key,
        "ts": ts or datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "liters": liter,
        "fuel_total_eur": kosten,
        "price_per_l": _preis_je_liter(kosten, liter),
        "discount": ersparnis,
        "station_name": station,
        "odometer_km": odometer_km,
        # Vorschlag aus der Differenz zum letzten Tankvorgang, aber wie
        # odometer_km selbst frei überschreibbar - deshalb nimmt ein manuell
        # übergebener Wert immer Vorrang vor der Berechnung.
        "distance_km": distanz if distanz is not None else profil.distanz_seit_letzter_tankung(odometer_km),
        "fuel_type": kraftstoff,
        "source": "beleg" if receipt_key else "manual",
        "status": "vollständig",
        "receipt_file": receipt_file,
        "edited_fields": [],
    }
    profil.tankvorgang_anhaengen(tankvorgang)
    frontend_veroeffentlichung.tankvorgaenge_veroeffentlichen()
    log.info(f"audi_dashboard: Tankvorgang {tankvorgang['tank_id']} angelegt")


@service
def audi_dashboard_tankvorgang_aktualisieren(tank_id=None, ts=None, liter=None, kosten=None, km=None, ersparnis=None, station=None, distanz=None, kraftstoff=None, receipt_key=None, receipt_file=None):
    """Bearbeitet einen bestehenden Tankvorgang (Einzelbeleg-Seite, egal ob
    ursprünglich per Beleg oder manuell angelegt). receipt_key/receipt_file
    sind optional: kommen sie mit (ein im Bearbeiten-Formular hochgeladener
    Beleg wurde in den Entwurf übernommen), wird der Tankvorgang zusätzlich
    als per Beleg erfasst markiert. Aufruf als
    pyscript.audi_dashboard_tankvorgang_aktualisieren."""
    if not tank_id:
        log.warning("audi_dashboard: tankvorgang_aktualisieren ohne tank_id aufgerufen")
        return
    aenderungen = {
        "ts": ts, "liters": liter, "fuel_total_eur": kosten,
        "price_per_l": _preis_je_liter(kosten, liter),
        "odometer_km": km, "discount": ersparnis, "station_name": station,
        "distance_km": distanz, "fuel_type": kraftstoff,
    }
    if receipt_key:
        aenderungen["receipt_key"] = receipt_key
        aenderungen["receipt_file"] = receipt_file
        aenderungen["source"] = "beleg"
    if profil.tankvorgang_aktualisieren(tank_id, aenderungen):
        frontend_veroeffentlichung.tankvorgaenge_veroeffentlichen()
        log.info(f"audi_dashboard: Tankvorgang {tank_id} aktualisiert")
    else:
        log.warning(f"audi_dashboard: Tankvorgang {tank_id} nicht gefunden")


@service
def audi_dashboard_tankvorgang_loeschen(tank_id=None):
    """Löscht einen Tankvorgang unwiderruflich (Löschfunktion in der
    Oberfläche). Aufruf als pyscript.audi_dashboard_tankvorgang_loeschen."""
    if not tank_id:
        log.warning("audi_dashboard: tankvorgang_loeschen ohne tank_id aufgerufen")
        return
    if profil.tankvorgang_loeschen(tank_id):
        frontend_veroeffentlichung.tankvorgaenge_veroeffentlichen()
        log.info(f"audi_dashboard: Tankvorgang {tank_id} gelöscht")
    else:
        log.warning(f"audi_dashboard: Tankvorgang {tank_id} nicht gefunden")
