"""Fahrgestellnummer (FIN/VIN) automatisch aus den zugeordneten Sensoren ableiten.

Die FIN ist ein reines Profilfeld (fahrzeugprofil.json, "Fahrzeug einrichten"),
ohne eigene Sensor-Zuordnung im Setup-Menü - obwohl mindestens eine der ohnehin
schon zugeordneten Integrationen sie meist bereits mitbringt: die
cupra_eu_data_act-Integration trägt sie als Geräte-Identifier/Seriennummer
(nicht als eigene Entität), der Teltonika FMM003 kann sie je nach Firmware-/
Kanal-Konfiguration ebenfalls liefern. Ein eigenes Setup-Feld dafür würde vom
Nutzer verlangen, denselben Wert doppelt zu suchen und zuzuordnen, obwohl die
Information am Gerät der ohnehin schon zugeordneten Sensoren hängt.

Ein Schalter "automatisch" neben dem FIN-Feld ("Fahrzeug einrichten",
`fahrzeug.fin_automatisch` im Profil, Standard an) entscheidet, ob diese
Ableitung überhaupt läuft. An: bei jedem Start, nach jeder Setup-Änderung und
nach jedem Profil-Speichern (also auch direkt beim Umschalten des Schalters
selbst) wird das Geräte-Register der AKTUELL zugeordneten Sensoren durchsucht
(nicht aller Entitäten der Instanz - nur was Sensorzuordnung tatsächlich
referenziert): für jede zugeordnete Entity-ID wird ihr Gerät nachgeschlagen und
daraus eine FIN-Kandidatin extrahiert (Geräte-Identifier oder Seriennummer, die
wie eine echte FIN aussieht - 17 Zeichen, ohne die im VIN-Standard verbotenen
Verwechslungsbuchstaben I/O/Q). Liefern mehrere Geräte je eine Kandidatin,
müssen sie übereinstimmen; weichen sie ab, wird nur gewarnt statt geraten -
dieselbe "kein Vorschlag ist besser als ein falscher" Haltung wie an anderen
Stellen dieser App (siehe z. B. entitaetIdKurz() im Panel). Solange
"automatisch" an ist, gilt der abgeleitete Wert als maßgeblich und wird bei
jedem Lauf aktualisiert (auch über einen vorherigen Wert hinweg) - genau das
ist der Zweck des Schalters, nicht ein "nur einmal beim leeren Feld" wie in
einer früheren Fassung dieses Moduls.

Aus: der Schalter deaktiviert die Ableitung vollständig, einschließlich des
Mehrfach-Sensor-Abgleichs oben - das Feld wird dann nirgends mehr angefasst
und bleibt reine Handeingabe wie jedes andere Profilfeld auch.
"""

from __future__ import annotations

import logging
import re
from dataclasses import fields
from typing import TYPE_CHECKING

from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import entity_registry as er

if TYPE_CHECKING:
    from .ablage import Ablage
    from .einstellungen import Sensorzuordnung
    from .zuordnung import Zuordnung

_LOGGER = logging.getLogger(__name__)

# 17 Zeichen, Großbuchstaben ohne I/O/Q (im VIN-Standard wegen Verwechslungs-
# gefahr mit 1/0 verboten) und Ziffern - erkennt eine FIN, ohne andere
# Geräte-Identifier (z. B. "FMM003 - Testintegratoin") fälschlich zu matchen.
_SIEHT_AUS_WIE_FIN = re.compile(r"^[A-HJ-NPR-Z0-9]{17}$")


def _zugeordnete_entity_ids(werte: Sensorzuordnung) -> list[str]:
    """Alle aktuell zugeordneten Entity-IDs, Listenfelder (Türen/Fenster ...)
    eingeschlossen - dieselbe Quelle, aus der auch das Panel seine
    Vergleichsbasis baut (setupAndereIdsOhneDomain() in audi-dashboard-app.js),
    hier nur serverseitig."""
    ids: list[str] = []
    for feld_info in fields(werte):
        wert = getattr(werte, feld_info.name)
        if isinstance(wert, list):
            ids.extend(eintrag for eintrag in wert if eintrag)
        elif wert:
            ids.append(wert)
    return ids


def _fin_kandidat_fuer_geraet(geraet: dr.DeviceEntry) -> str | None:
    werte = [geraet.serial_number] + [kennung for _domain, kennung in geraet.identifiers]
    for wert in werte:
        if wert and _SIEHT_AUS_WIE_FIN.match(wert.upper()):
            return wert.upper()
    return None


def fin_kandidaten_ermitteln(hass: HomeAssistant, werte: Sensorzuordnung) -> dict[str, str]:
    """{Geräte-ID: FIN-Kandidatin} für jedes Gerät hinter einer aktuell
    zugeordneten Entität, dessen Geräte-Identifier/Seriennummer wie eine
    echte FIN aussieht."""
    ent_reg = er.async_get(hass)
    dev_reg = dr.async_get(hass)
    gefunden: dict[str, str] = {}
    for entity_id in _zugeordnete_entity_ids(werte):
        eintrag = ent_reg.async_get(entity_id)
        if not eintrag or not eintrag.device_id:
            continue
        geraet = dev_reg.async_get(eintrag.device_id)
        if not geraet:
            continue
        kandidat = _fin_kandidat_fuer_geraet(geraet)
        if kandidat:
            gefunden[geraet.id] = kandidat
    return gefunden


async def fin_ableiten_und_uebernehmen(
    hass: HomeAssistant, zuordnung: Zuordnung, ablage: Ablage
) -> None:
    """Hält die FIN im Profil mit den zugeordneten Sensoren synchron, solange
    fahrzeug.fin_automatisch nicht ausdrücklich abgeschaltet ist (Standard:
    an)."""
    profil = await ablage.profil_lesen()
    if profil is None:
        return
    fahrzeug = profil.setdefault("fahrzeug", {})
    if not fahrzeug.get("fin_automatisch", True):
        return

    kandidaten = fin_kandidaten_ermitteln(hass, zuordnung.werte)
    eindeutig = set(kandidaten.values())
    if not eindeutig:
        # Nichts gefunden - einen eventuell schon eingetragenen Wert nicht
        # löschen, nur weil gerade kein Kandidat da ist (z. B. Sensoren
        # kurzzeitig nicht verfügbar).
        return
    if len(eindeutig) > 1:
        _LOGGER.warning(
            "Fahrgestellnummer nicht eindeutig aus den zugeordneten Sensoren "
            "ableitbar (gefunden: %s) - bitte \"automatisch\" in \"Fahrzeug "
            "einrichten\" ausschalten und von Hand eintragen.",
            ", ".join(sorted(eindeutig)),
        )
        return

    fin = eindeutig.pop()
    if fahrzeug.get("fin") == fin:
        return
    fahrzeug["fin"] = fin
    await ablage.profil_schreiben(profil)
    _LOGGER.info(
        "Fahrgestellnummer automatisch aus den zugeordneten Sensoren übernommen: %s", fin
    )
