/* ================================================================
   Datenschicht DataMetric360 - Sammelexport
   ================================================================
   Ein Zugang für die gesamte Oberfläche: REST für Lesen und Schreiben,
   WebSocket für Push, Warteschlange fürs Funkloch. Welche Bildschirme es
   gibt, ist hier bewusst unbekannt - diese Schicht überlebt jeden
   Entwurfsdurchlauf unverändert. */

export * from "./types.ts";
export * from "./umgebung.ts";
export * from "./rest.ts";
export * from "./live.ts";
export * from "./warteschlange.ts";
export * from "./ablageNativ.ts";

import { DIENST_DOMAIN, ENTITAETEN } from "./types.ts";
import type {
  AppVersionAngabe,
  Fahrt,
  Fahrzeugstatus,
  ImportErgebnis,
  Profil,
  Tankvorgang,
} from "./types.ts";
import { HassRest } from "./rest.ts";
import { HassLive } from "./live.ts";
import { Warteschlange } from "./warteschlange.ts";

/** Eingabefelder für Tankvorgänge. Die Namen sind die des Backends
    (belegverarbeitung.py), nicht die des Datensatzes - deshalb "liter" statt
    "liters" und "kosten" statt "fuel_total_eur". */
export interface TankvorgangFelder {
  ts?: string;
  liter?: number | null;
  kosten?: number | null;
  km?: number | null;
  ersparnis?: number | null;
  station?: string | null;
  distanz?: number | null;
  kraftstoff?: string | null;
}

/** Eingabefelder für Fahrten - Namen wie beim Backend (fahrterkennung.py:
    fahrt_manuell_anlegen/fahrt_aktualisieren).
    Alles außer den Zeitpunkten ist optional: leer bleibt es liegen, bis das
    Kilometerstand-Screening (§7.2) oder eine spätere Bearbeitung es füllt. */
export interface FahrtFelder {
  ts_start?: string;
  ts_end?: string;
  art?: "privat" | "arbeitsweg";
  start_ort?: string | null;
  ziel_ort?: string | null;
  odo_start?: number | null;
  odo_end?: number | null;
  distanz?: number | null;
}

/** Bündelt die drei Bausteine und bildet die Fachvorgänge ab, die das alte
    Dashboard über hass.callService erledigt hat. */
export class DataMetricApi {
  readonly rest: HassRest;
  readonly live: HassLive;
  readonly warteschlange: Warteschlange;

  constructor() {
    this.rest = new HassRest();
    this.live = new HassLive();
    this.warteschlange = new Warteschlange(this.rest);

    /* Sobald die Verbindung wieder steht, wartende Änderungen nachliefern -
       ohne dass die Oberfläche etwas anstoßen muss. */
    this.live.aufVerbindung((zustand) => {
      if (zustand === "verbunden") void this.warteschlange.abarbeiten();
    });
  }

  /* ------------------------------------------------------------ Lesen */

  profilLesen(): Promise<Profil> {
    return this.rest.datenLesen<Profil>(ENTITAETEN.profil);
  }

  fahrtenLesen(): Promise<Fahrt[]> {
    return this.rest.datenLesen<Fahrt[]>(ENTITAETEN.fahrten);
  }

  tankvorgaengeLesen(): Promise<Tankvorgang[]> {
    return this.rest.datenLesen<Tankvorgang[]>(ENTITAETEN.tankvorgaenge);
  }

  fahrzeugstatusLesen(): Promise<Fahrzeugstatus> {
    return this.rest.datenLesen<Fahrzeugstatus>(ENTITAETEN.fahrzeugstatus);
  }

  /* --------------------------------------------------------- Schreiben
     Alle schreibenden Vorgänge laufen über die Warteschlange, nicht direkt
     über rest.dienstAufrufen(). Damit verhält sich die App im Funkloch
     genauso wie mit Netz - nur eben zeitversetzt. */

  profilSchreiben(profil: Profil): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "profil_schreiben",
      { profil_json: JSON.stringify(profil) },
      "Fahrzeugdaten speichern",
    );
  }

  belegHochladen(pdfBase64: string, dateiname: string, tankId?: string): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "beleg_hochladen",
      { pdf_base64: pdfBase64, dateiname, ...(tankId ? { tank_id: tankId } : {}) },
      `Beleg „${dateiname}" hochladen`,
    );
  }

  /** Legt eine Fahrt von Hand an — für Fahrten ohne automatische Erkennung. */
  fahrtAnlegen(felder: FahrtFelder): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "fahrt_manuell_anlegen",
      { ...felder },
      "Fahrt eintragen",
    );
  }

  /** Bearbeitet eine bestehende Fahrt, gleich ob automatisch erkannt oder von
      Hand angelegt. */
  fahrtAktualisieren(tripId: string, felder: FahrtFelder): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "fahrt_aktualisieren",
      { trip_id: tripId, ...felder },
      "Fahrt ändern",
    );
  }

  fahrtLoeschen(tripId: string): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "fahrt_loeschen",
      { trip_id: tripId },
      "Fahrt löschen",
    );
  }

  /** Felder eines Tankvorgangs, wie sie das Backend erwartet (deutsche
      Parameternamen — belegverarbeitung.py). */
  tankvorgangAnlegen(felder: TankvorgangFelder): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "tankvorgang_manuell",
      { ...felder },
      "Tankvorgang eintragen",
    );
  }

  tankvorgangAktualisieren(tankId: string, felder: TankvorgangFelder): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "tankvorgang_aktualisieren",
      { tank_id: tankId, ...felder },
      "Tankvorgang ändern",
    );
  }

  tankvorgangLoeschen(tankId: string): Promise<unknown> {
    return this.warteschlange.einreihen(
      DIENST_DOMAIN,
      "tankvorgang_loeschen",
      { tank_id: tankId },
      "Tankvorgang löschen",
    );
  }

  /** Stößt eine sofortige Aktualisierung im Backend an (Pull-to-refresh). */
  jetztAktualisieren(): Promise<unknown> {
    return this.rest.dienstAufrufen(DIENST_DOMAIN, "jetzt_aktualisieren");
  }

  /** Nutzlast von sensor.audi_dashboard_app_version: welchen Stand das
      Backend ausliefert (`app`, siehe VERSIONIERUNG.md) und ob ein
      OTA-Bündel bereitliegt (`buendel`, siehe daten/ota.ts). Beides kommt
      aus derselben Entität und wird deshalb in einem Aufruf gelesen statt in
      zweien. `null` bei jedem Fehler — fehlende Entität (älteres Backend),
      Netzproblem, was auch immer; dann wird nichts verglichen und nichts
      gemeldet, statt einen Fehlalarm auszulösen. */
  async appVersionAngabeLesen(): Promise<AppVersionAngabe | null> {
    try {
      const zustand = await this.rest.zustandLesen<{ daten?: AppVersionAngabe }>(
        ENTITAETEN.appVersion,
      );
      return zustand.attributes?.daten ?? null;
    } catch {
      return null;
    }
  }

  /* ------------------------------------------------- Selbst-Update der
     Integration (aktualisierung.py) - Ersatz für install.ps1, das Windows
     Smart App Control blockiert. Bewusst NICHT über die Warteschlange, wie
     historieImportieren() oben: beides sind ausdrückliche, sofortige
     Aktionen, kein Zustand, den man im Funkloch absetzt und später
     nachgeholt haben will. Ergebnis kommt über
     appVersionAngabeLesen()/daten.integration_update zurück, nicht über den
     Rückgabewert dieser Aufrufe. */

  /** Fragt nur nach, ob eine neuere Fassung vorliegt - lädt nichts herunter. */
  updatePruefen(): Promise<unknown> {
    return this.rest.dienstAufrufen(DIENST_DOMAIN, "update_pruefen");
  }

  /** Lädt die neueste Fassung und ersetzt den Integrationsordner. Home
      Assistant muss danach neu gestartet werden. */
  updateInstallieren(): Promise<unknown> {
    return this.rest.dienstAufrufen(DIENST_DOMAIN, "update_installieren");
  }

  /** Startet Home Assistant komplett neu - der letzte Schritt nach
      updateInstallieren(), damit die neue Fassung tatsächlich geladen wird
      (ein reiner Reload reicht dafür nicht, siehe AGENTS.md Abschnitt J).
      Kein audi_dashboard-Dienst, deshalb der Bereich "homeassistant" statt
      DIENST_DOMAIN. */
  homeAssistantNeuStarten(): Promise<unknown> {
    return this.rest.dienstAufrufen("homeassistant", "restart");
  }

  /* ------------------------------------------- Import aus dem HA-Verlauf
     Bewusst NICHT über die Warteschlange, anders als die übrigen
     schreibenden Vorgänge: der Import ist keine Eingabe, die man im Funkloch
     absetzt und später ausgeführt haben will. Er dauert lange, sein Ergebnis
     ist der eigentliche Zweck des Aufrufs, und ein nachträglich aus der
     Warteschlange abgefeuerter Lauf käme für den Nutzer aus dem Nichts.
     Ohne Netz gehört er schlicht nicht angeboten. */

  /** Startet den Import. Kehrt zurück, sobald das Backend den Auftrag
      angenommen hat — nicht, wenn er fertig ist; dafür importStatusLesen(). */
  historieImportieren(start: string, ende: string): Promise<unknown> {
    return this.rest.dienstAufrufen(DIENST_DOMAIN, "historie_importieren", {
      start,
      ende,
    });
  }

  /** Stand des laufenden bzw. letzten Imports: "laeuft" | "fertig" |
      "fehler", plus Zählwerte im Attribut "daten". */
  async importStatusLesen(): Promise<{ zustand: string; daten: ImportErgebnis }> {
    const zustand = await this.rest.zustandLesen<{ daten: ImportErgebnis }>(
      ENTITAETEN.importStatus,
    );
    return { zustand: zustand.state, daten: zustand.attributes?.daten ?? {} };
  }
}
