"""Reifenzähler: jeder gefahrene Kilometer wird dem Satz zugerechnet, der zum
Zeitpunkt der Fahrt aktiv war.

Früher war der Zähler = aktueller Kilometerstand minus fester Startwert je
Satz. Das war falsch, sobald einmal zwischen Sommer und Winter umgeschaltet
wurde: die Differenz zum Startwert zählt dann alle seither gefahrenen
Kilometer mit, auch die auf dem jeweils anderen Satz. Jetzt führt
reifen.saetze.<satz>.km selbst den fortlaufenden Gesamtstand, und
reifen.referenz_odo_km hält den zuletzt verarbeiteten Kilometerstand fest,
gegen den die nächste Änderung als Delta gemessen wird.

Keine Abhängigkeit von total_increasing oder utility_meter: die Fortschreibung
braucht nur den zuletzt bekannten Absolutwert des Kilometerstand-Sensors. Ein
zeitweise "unavailable" Quellsensor verliert dadurch nichts - referenz_odo_km
bleibt einfach stehen, bis wieder ein gültiger Wert kommt, und das nächste
Delta wird dann gegen diesen alten Stand berechnet. Auch Kilometer, die
während eines HA-Ausfalls gefahren wurden, werden so noch dem damals aktiven
Satz zugerechnet.
"""

from __future__ import annotations

import datetime
import logging
from typing import TYPE_CHECKING

from .ablage import neue_id
from .veroeffentlichung import zustand_oder_none

if TYPE_CHECKING:
    from .koordinator import Koordinator

_LOGGER = logging.getLogger(__name__)

SAETZE = ("sommer", "winter")

# Felder, die bei "Neue Räder anlegen" mit auf den frischen Satz übernommen
# werden - Größe und Solldruck ändern sich bei einem gleich montierten Ersatz
# typischerweise nicht, anders als Marke/Modell/DOT (siehe archivieren()).
_UEBERNOMMENE_FELDER = ("bild", "mass", "druck_vorne", "druck_hinten")


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


async def fortschreiben(k: Koordinator, neuer_km: float | None, neuer_aktiv: str | None = None) -> None:
    """Rechnet zuerst mit dem bisher aktiven Satz ab (Delta seit
    referenz_odo_km), trägt danach optional einen Satzwechsel ein - in dieser
    Reihenfolge, damit beim Wechseln selbst keine Kilometer dem neuen Satz
    zugerechnet werden, die eigentlich noch auf dem alten gefahren wurden."""
    profil = await k.ablage.profil_lesen()
    if profil is None:
        return

    reifen = profil.setdefault("reifen", {})
    saetze = reifen.setdefault("saetze", {})
    aktiv = reifen.get("aktiv", "sommer")
    referenz = reifen.get("referenz_odo_km")

    if referenz is not None and neuer_km is not None:
        delta = round(neuer_km - referenz, 1)
        if delta > 0:
            satz = saetze.setdefault(aktiv, {})
            satz["km"] = round((satz.get("km") or 0) + delta, 1)
    if neuer_km is not None:
        reifen["referenz_odo_km"] = neuer_km
    if neuer_aktiv is not None:
        reifen["aktiv"] = neuer_aktiv

    await k.ablage.profil_schreiben(profil)
    await k.profil_veroeffentlichen(profil)


async def wechseln(k: Koordinator, satz: str | None) -> None:
    if satz not in SAETZE:
        _LOGGER.warning("Ungültiger Reifensatz '%s'", satz)
        return
    await fortschreiben(k, aktueller_km(k), neuer_aktiv=satz)
    _LOGGER.info("Aktiver Reifensatz auf '%s' umgeschaltet", satz)


async def km_setzen(k: Koordinator, satz: str | None, km: object) -> None:
    """Manuelle Korrektur des gefahrenen Kilometerstands eines Reifensatzes.

    Überschreibt nur den gespeicherten Zähler selbst - referenz_odo_km (der
    zuletzt verarbeitete Fahrzeug-Kilometerstand) bleibt unverändert, damit
    die nächste Fortschreibung weiterhin korrekt von hier aus weiterzählt,
    statt auf den neuen Wert erneut ein Delta seit dem alten Stand
    aufzuschlagen."""
    if satz not in SAETZE:
        _LOGGER.warning("Ungültiger Reifensatz '%s'", satz)
        return
    try:
        wert = round(float(km), 1)  # type: ignore[arg-type]
    except (TypeError, ValueError):
        _LOGGER.warning("Ungültiger km-Wert '%s'", km)
        return
    if wert < 0:
        _LOGGER.warning("Negativer km-Wert '%s' abgelehnt", wert)
        return

    profil = await k.ablage.profil_lesen()
    if profil is None:
        return
    reifen = profil.setdefault("reifen", {})
    reifen.setdefault("saetze", {}).setdefault(satz, {})["km"] = wert
    await k.ablage.profil_schreiben(profil)
    await k.profil_veroeffentlichen(profil)
    _LOGGER.info("km-Stand '%s' manuell auf %s gesetzt", satz, wert)


async def archivieren(k: Koordinator, satz: str | None) -> None:
    """"Neue Räder anlegen": legt den aktuellen Stand von `satz` (km, Marke,
    Modell, DOT, Kommentar) als Übersichtszeile ins Archiv und setzt den
    laufenden Satz auf einen frischen Stand zurück - km auf 0, Marke/Modell/
    DOT/Kommentar leer (galten für den alten, jetzt ersetzten Reifen), Größe
    und Solldruck unverändert übernommen (siehe _UEBERNOMMENE_FELDER)."""
    if satz not in SAETZE:
        _LOGGER.warning("Ungültiger Reifensatz '%s'", satz)
        return

    profil = await k.ablage.profil_lesen()
    if profil is None:
        return
    reifen = profil.setdefault("reifen", {})
    saetze = reifen.setdefault("saetze", {})
    alt = dict(saetze.get(satz, {}))

    archiv = reifen.setdefault("archiv", [])
    archiv.insert(0, {
        "id": neue_id("r"),
        "satz": satz,
        "ersetzt_am": datetime.date.today().isoformat(),
        "km": alt.get("km") or 0,
        "marke": alt.get("marke") or "",
        "modell": alt.get("modell") or "",
        "dot": alt.get("dot") or "",
        "mass": alt.get("mass") or "",
        "druck_vorne": alt.get("druck_vorne") or "",
        "druck_hinten": alt.get("druck_hinten") or "",
        "kommentar": alt.get("kommentar") or "",
    })

    saetze[satz] = {
        "km": 0, "marke": "", "modell": "", "dot": "", "kommentar": "",
        **{feld: alt.get(feld) or "" for feld in _UEBERNOMMENE_FELDER},
    }

    await k.ablage.profil_schreiben(profil)
    await k.profil_veroeffentlichen(profil)
    _LOGGER.info("Reifensatz '%s' archiviert (%s km), neuer Satz angelegt", satz, archiv[0]["km"])


async def archiv_aktualisieren(k: Koordinator, id_: str | None, daten: dict) -> None:
    """Bearbeitet einen archivierten Reifensatz-Eintrag (Übersichtszeile auf
    der Reifen-Seite, aufgeklappt -> "Bearbeiten")."""
    if not id_:
        _LOGGER.warning("reifen_archiv_aktualisieren ohne id aufgerufen")
        return
    profil = await k.ablage.profil_lesen()
    if profil is None:
        return
    archiv = profil.setdefault("reifen", {}).setdefault("archiv", [])
    eintrag = next((e for e in archiv if e.get("id") == id_), None)
    if eintrag is None:
        _LOGGER.warning("Archivierter Reifensatz '%s' nicht gefunden", id_)
        return

    erlaubte_felder = (
        "ersetzt_am", "km", "marke", "modell", "dot", "mass",
        "druck_vorne", "druck_hinten", "kommentar",
    )
    for feld in erlaubte_felder:
        if feld in daten and daten[feld] is not None:
            eintrag[feld] = daten[feld]

    await k.ablage.profil_schreiben(profil)
    await k.profil_veroeffentlichen(profil)
    _LOGGER.info("Archivierter Reifensatz '%s' aktualisiert", id_)


async def archiv_loeschen(k: Koordinator, id_: str | None) -> None:
    if not id_:
        _LOGGER.warning("reifen_archiv_loeschen ohne id aufgerufen")
        return
    profil = await k.ablage.profil_lesen()
    if profil is None:
        return
    reifen = profil.setdefault("reifen", {})
    archiv = reifen.get("archiv", [])
    uebrig = [e for e in archiv if e.get("id") != id_]
    if len(uebrig) == len(archiv):
        _LOGGER.warning("Archivierter Reifensatz '%s' nicht gefunden", id_)
        return
    reifen["archiv"] = uebrig
    await k.ablage.profil_schreiben(profil)
    await k.profil_veroeffentlichen(profil)
    _LOGGER.info("Archivierter Reifensatz '%s' gelöscht", id_)


def zustaende(profil: dict | None) -> dict[str, object]:
    """Die drei Kontrollwerte für die eigenen Reifen-Entitäten.

    Das Frontend liest die Zahlen direkt aus dem veröffentlichten Profil -
    diese Entitäten sind nur zur Kontrolle in Entwicklerwerkzeuge -> Zustände
    und für eigene Automationen des Nutzers da."""
    reifen = (profil or {}).get("reifen", {})
    saetze = reifen.get("saetze", {})
    return {
        "sommer": saetze.get("sommer", {}).get("km"),
        "winter": saetze.get("winter", {}).get("km"),
        "aktiv": reifen.get("aktiv") or "unbekannt",
    }
