"""Belegverarbeitung für Tankbelege (SPECIFICATION.md §7.7).

Beleg-Upload direkt in der App statt Postfachanbindung - kein IMAP-Zugriff.
Das eigentliche Lesen der PDF macht shell_beleg_parser.py daneben; hier steht,
was mit dem Ergebnis passiert.

ZWEI REGELN, DIE DEN AUFBAU ERKLÄREN
------------------------------------
1. Liter, Kosten und Ersparnis kommen vom Beleg, Kilometerstand und gefahrene
   Distanz kommen immer vom Fahrzeug (§7.7 Regel 2). Ein Beleg-Nachtrag rührt
   odometer_km/distance_km deshalb nicht an.
2. price_per_l wird grundsätzlich aus fuel_total_eur ÷ liters berechnet, nie
   vom Beleg übernommen. fuel_total_eur ist dabei immer der reale, bereits
   rabattierte Betrag; die SmartDeal-Ersparnis steht separat in discount.

Mit tank_id trägt der Upload die Belegdaten in einen bestehenden Tankvorgang
nach - egal ob der ursprünglich manuell oder automatisch angelegt wurde. Passt
das Belegdatum nicht zum erfassten Tankvorgang (anderer Kalendertag), wird
nichts übernommen, sondern ein Fehler veröffentlicht: sonst landet in der
Oberfläche versehentlich ein falscher Beleg an einem bestehenden Eintrag.

Ohne tank_id legt der Upload KEINEN eigenständigen Tankvorgang an, sondern
parst nur und veröffentlicht das Ergebnis - die Oberfläche übernimmt die Werte
in den gerade offenen Entwurf und speichert erst auf einen expliziten Klick.
"""

from __future__ import annotations

import base64
import datetime
import logging
import os
from typing import TYPE_CHECKING

from . import shell_beleg_parser
from .tankerkennung import leerer_tankvorgang
from .veroeffentlichung import zustand_oder_none

if TYPE_CHECKING:
    from .koordinator import Koordinator

_LOGGER = logging.getLogger(__name__)


def preis_je_liter(fuel_total_eur: float | None, liters: float | None) -> float | None:
    if not liters or fuel_total_eur is None:
        return None
    return round(fuel_total_eur / liters, 3)


def _speichern_und_lesen(ordner: str, dateiname: str, rohdaten: bytes) -> tuple[str, dict]:
    """Läuft komplett im Executor: Datei schreiben und PDF lesen sind beides
    blockierende Arbeit."""
    os.makedirs(ordner, exist_ok=True)
    pfad = os.path.join(ordner, dateiname)
    with open(pfad, "wb") as datei:
        datei.write(rohdaten)
    return pfad, shell_beleg_parser.beleg_lesen(pfad)


async def hochladen(k: Koordinator, daten: dict) -> None:
    pdf_base64 = daten.get("pdf_base64")
    tank_id = daten.get("tank_id")
    if not pdf_base64:
        _LOGGER.warning("Beleg-Upload ohne pdf_base64 aufgerufen")
        return

    dateiname = daten.get("dateiname") or (
        f"beleg_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
    )
    # Alles bis zum geparsten Ergebnis in einem try: eine unbehandelte
    # Ausnahme hier (kaputtes Base64, volle Festplatte, unlesbares PDF) wäre
    # für die Oberfläche unsichtbar - ein abgelehnter Dienstaufruf landet dort
    # nur in der Browser-Konsole, angezeigt wird nichts.
    try:
        rohdaten = base64.b64decode(pdf_base64)
        pdf_pfad, geparst = await k.hass.async_add_executor_job(
            _speichern_und_lesen, k.ablage.belege_ordner, dateiname, rohdaten
        )
    except Exception as fehler:  # noqa: BLE001 - jede Ursache endet hier gleich
        _LOGGER.error("Beleg konnte nicht verarbeitet werden: %s", fehler)
        await k.beleg_ergebnis_veroeffentlichen(
            {"fehler": "Beleg konnte nicht gelesen werden", "tank_id": tank_id}
        )
        return

    receipt_key = geparst.get("receipt_key")
    if not receipt_key:
        _LOGGER.error("Parser lieferte keinen receipt_key")
        await k.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 = await k.ablage.tankvorgang_nach_receipt_key(receipt_key)
    if vorhandener is not None and vorhandener.get("tank_id") != tank_id:
        _LOGGER.info("Beleg %s bereits vorhanden, übersprungen", receipt_key)
        await k.beleg_ergebnis_veroeffentlichen(
            {"fehler": "Beleg bereits erfasst", "tank_id": tank_id}
        )
        return

    beleg_felder = _beleg_felder(geparst, pdf_pfad)

    if not tank_id:
        await k.beleg_ergebnis_veroeffentlichen({**beleg_felder, "tank_id": None})
        _LOGGER.info("Beleg %s geparst, wartet auf Speichern in der Oberfläche", receipt_key)
        return

    await _nachtragen(k, tank_id, geparst, beleg_felder, receipt_key)


def _beleg_felder(geparst: dict, pdf_pfad: str) -> dict:
    liters = geparst.get("liters")
    fuel_total_eur = geparst.get("fuel_total_eur")  # real, bereits rabattiert
    return {
        "receipt_key": geparst.get("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"),
        "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,
    }


async def _nachtragen(
    k: Koordinator, tank_id: str, geparst: dict, beleg_felder: dict, receipt_key: str
) -> None:
    vorhanden = await k.ablage.tankvorgang_nach_id(tank_id)
    if not vorhanden:
        _LOGGER.warning("Tankvorgang %s für Beleg-Nachtrag nicht gefunden", tank_id)
        await k.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:
        _LOGGER.warning(
            "Belegdatum %s passt nicht zu Tankvorgang %s (Datum %s)",
            belegdatum, tank_id, vorhandenes_datum,
        )
        await k.beleg_ergebnis_veroeffentlichen(
            {"fehler": "Falsches Belegdatum", "tank_id": tank_id}
        )
        return

    aenderungen = dict(beleg_felder)
    aenderungen["status"] = "vollständig"
    if await k.ablage.tankvorgang_aktualisieren(tank_id, aenderungen):
        await k.tankvorgaenge_veroeffentlichen()
        await k.beleg_ergebnis_veroeffentlichen({"erfolg": True, "tank_id": tank_id})
        _LOGGER.info("Beleg %s in Tankvorgang %s nachgetragen", receipt_key, tank_id)
    else:
        await k.beleg_ergebnis_veroeffentlichen(
            {"fehler": "Tankvorgang nicht gefunden", "tank_id": tank_id}
        )


# ------------------------------------------------ Tankvorgänge aus dem Formular

async def tankvorgang_manuell(k: Koordinator, daten: dict) -> None:
    """Erfassung aus dem Formular "Neuer Tankvorgang".

    receipt_key/receipt_file sind optional: kommen sie mit (weil zuvor ein
    Beleg ohne tank_id in den Entwurf übernommen wurde), zählt der Tankvorgang
    als per Beleg erfasst, sonst als manuell."""
    liter = daten.get("liter")
    kosten = daten.get("kosten")
    if liter is None or kosten is None:
        _LOGGER.warning("liter und kosten sind Pflichtfelder")
        return

    km = daten.get("km")
    odometer_km = km if km is not None else _km_aus_sensor(k)
    distanz = daten.get("distanz")
    if distanz is None:
        # Vorschlag aus der Differenz zum letzten Tankvorgang, aber wie
        # odometer_km selbst frei überschreibbar - ein übergebener Wert hat
        # deshalb immer Vorrang vor der Berechnung.
        distanz = await k.ablage.distanz_seit_letzter_tankung(odometer_km)

    receipt_key = daten.get("receipt_key")
    tankvorgang = leerer_tankvorgang(
        daten.get("ts") or datetime.datetime.now(datetime.UTC).isoformat(),
        "beleg" if receipt_key else "manual",
    )
    tankvorgang.update({
        "receipt_key": receipt_key,
        "liters": liter,
        "fuel_total_eur": kosten,
        "price_per_l": preis_je_liter(kosten, liter),
        "discount": daten.get("ersparnis"),
        "station_name": daten.get("station"),
        "odometer_km": odometer_km,
        "distance_km": distanz,
        "fuel_type": daten.get("kraftstoff"),
        "status": "vollständig",
        "receipt_file": daten.get("receipt_file"),
    })

    await k.ablage.tankvorgang_anhaengen(tankvorgang)
    await k.tankvorgaenge_veroeffentlichen()
    _LOGGER.info("Tankvorgang %s angelegt", tankvorgang["tank_id"])


def _km_aus_sensor(k: Koordinator) -> float | None:
    wert = zustand_oder_none(k.hass, k.zuordnung.werte.KM_SENSOR)
    try:
        return float(wert)  # type: ignore[arg-type]
    except (TypeError, ValueError):
        return None


async def tankvorgang_aktualisieren(k: Koordinator, daten: dict) -> None:
    """Bearbeitet einen bestehenden Tankvorgang (Einzelbeleg-Seite, egal ob
    ursprünglich per Beleg oder manuell angelegt)."""
    tank_id = daten.get("tank_id")
    if not tank_id:
        _LOGGER.warning("tankvorgang_aktualisieren ohne tank_id aufgerufen")
        return

    liter = daten.get("liter")
    kosten = daten.get("kosten")
    aenderungen = {
        "ts": daten.get("ts"),
        "liters": liter,
        "fuel_total_eur": kosten,
        "price_per_l": preis_je_liter(kosten, liter),
        "odometer_km": daten.get("km"),
        "discount": daten.get("ersparnis"),
        "station_name": daten.get("station"),
        "distance_km": daten.get("distanz"),
        "fuel_type": daten.get("kraftstoff"),
    }
    if daten.get("receipt_key"):
        aenderungen["receipt_key"] = daten["receipt_key"]
        aenderungen["receipt_file"] = daten.get("receipt_file")
        aenderungen["source"] = "beleg"

    if await k.ablage.tankvorgang_aktualisieren(tank_id, aenderungen):
        await k.tankvorgaenge_veroeffentlichen()
        _LOGGER.info("Tankvorgang %s aktualisiert", tank_id)
    else:
        _LOGGER.warning("Tankvorgang %s nicht gefunden", tank_id)


async def tankvorgang_loeschen(k: Koordinator, tank_id: str | None) -> None:
    if not tank_id:
        _LOGGER.warning("tankvorgang_loeschen ohne tank_id aufgerufen")
        return
    if await k.ablage.tankvorgang_loeschen(tank_id):
        await k.tankvorgaenge_veroeffentlichen()
        _LOGGER.info("Tankvorgang %s gelöscht", tank_id)
    else:
        _LOGGER.warning("Tankvorgang %s nicht gefunden", tank_id)
