/**
 * Oberflächen-Updates ohne Xcode („OTA").
 *
 * Die native Hülle trägt ihre Oberfläche fest gebündelt — sie bleibt auf dem
 * Stand vom Signieren, unbegrenzt. Diese Datei ist der Ausweg: die App lädt
 * ein neues Bündel von der eigenen Home-Assistant-Instanz und tauscht es aus.
 *
 * WOHER DAS BÜNDEL KOMMT
 * ----------------------
 * Aus der Integration selbst: `custom_components/audi_dashboard/frontend/app/`
 * enthält `bundle.zip` und `bundle.json`, ausgeliefert unter
 * `/audi_dashboard_static/app/`. Damit reist das Bündel bei jedem
 * HACS-Update mit, und es kann gar nicht zur Integration unpassend sein — es
 * ist dieselbe Lieferung. Gebaut wird es von
 * `companion-app/scripts/ota-paket.ps1`.
 *
 * Welche Fassung bereitliegt, sagt `sensor.audi_dashboard_app_version` im
 * Attribut `daten.buendel`. Kein zweiter Abruf, keine zweite Quelle.
 *
 * WAS OTA NICHT KANN
 * ------------------
 * Nur Weboberfläche — HTML, CSS, JavaScript, Bilder. Alles Native (Plugins,
 * Capacitor selbst, Berechtigungen, iOS-Einstellungen) braucht weiterhin
 * Xcode. Deshalb setzt `resetWhenUpdate` in capacitor.config.ts nach einer
 * frischen Xcode-Installation wieder auf das eingebaute Bündel zurück: sonst
 * überdeckte ein älteres OTA-Bündel genau die native Änderung, für die man
 * Xcode gebraucht hat.
 *
 * DIE RÜCKFALLEBENE
 * -----------------
 * `startklarMelden()` unten muss nach jedem Start laufen. Bleibt die Meldung
 * aus (weil das neue Bündel gar nicht erst hochkommt), kehrt das Plugin nach
 * `appReadyTimeout` von selbst zum vorherigen Bündel zurück. Ein kaputtes
 * Bündel kann das Gerät deshalb nicht dauerhaft unbrauchbar machen — man
 * landet wieder da, wo man vorher war.
 */

import { Capacitor } from "@capacitor/core"
import { CapacitorUpdater } from "@capgo/capacitor-updater"

import type { Buendelangabe } from "../api"

/** Läuft die App in der nativen Hülle? Nur dort gibt es etwas auszutauschen —
    im Browser und im HA-Panel lädt ohnehin jeder Seitenaufruf den aktuellen
    Stand. */
export function otaMoeglich(): boolean {
  return Capacitor.isNativePlatform()
}

/**
 * Meldet dem Plugin, dass dieses Bündel hochgekommen ist.
 *
 * Ohne diesen Aufruf rollt das Plugin nach kurzer Zeit auf das vorherige
 * Bündel zurück — was genau richtig ist, wenn ein Update die App zerlegt hat.
 * Deshalb steht der Aufruf bewusst dort, wo React nachweislich gerendert hat
 * (App.tsx), und nicht schon in main.tsx: ein Absturz beim ersten Rendern
 * soll als Fehlschlag gelten und den Rückfall auslösen.
 */
export async function startklarMelden(): Promise<void> {
  if (!otaMoeglich()) return
  try {
    await CapacitorUpdater.notifyAppReady()
  } catch (fehler) {
    // Nur protokollieren: Scheitert die Meldung, greift der Rückfall — das
    // ist unangenehm, aber sicher. Ein Absturz an dieser Stelle wäre schlimmer.
    console.warn("OTA: notifyAppReady fehlgeschlagen", fehler)
  }
}

/** Ist ein anderes Bündel verfügbar als das gerade laufende?
    Dieselbe Vorsicht wie in versionVergleichen() (appVersion.ts): kennt die
    App ihre eigene Version nicht (eigene === null — praktisch nur im
    Dev-Server, ein natives Release baut nie ohne __APP_VERSION__), gilt das
    als "nicht vergleichbar", nicht als "abweichend". Alles andere böte einen
    Update-Knopf an, dessen Ziel man mit der laufenden Fassung gar nicht
    abgleichen konnte. */
export function buendelPasst(buendel: Buendelangabe | null, eigene: string | null): boolean {
  if (!buendel?.version || !buendel.url || !buendel.sha256 || !eigene) return false
  // Das Bündel muss zu der Fassung gehören, die diese Installation
  // ausliefert. Läge dort ein älteres, wäre ein "Update" ein Rückschritt.
  return buendel.version !== eigene
}

export class OtaFehler extends Error {}

/**
 * Lädt das Bündel und schaltet darauf um.
 *
 * Kehrt im Erfolgsfall **nie** zurück: `set()` zerstört den JavaScript-Kontext
 * und lädt die App neu. Alles, was danach stünde, liefe nicht mehr — deshalb
 * steht hier auch keine Erfolgsmeldung. Der Erfolg ist, dass die App neu
 * startet.
 *
 * @param basisUrl Adresse der Home-Assistant-Instanz, wie beim Einrichten
 *                 hinterlegt. Die Bündel-URL aus der Entität ist ein Pfad.
 */
export async function buendelAnwenden(
  buendel: Buendelangabe,
  basisUrl: string,
): Promise<never> {
  if (!otaMoeglich()) throw new OtaFehler("Updates gibt es nur in der App auf dem Gerät.")

  const url = new URL(buendel.url, basisUrl).href
  let geladen
  try {
    geladen = await CapacitorUpdater.download({
      url,
      version: buendel.version,
      // Das Plugin rechnet auf dem Gerät SHA-256 über die heruntergeladene
      // Zip und verwirft sie bei Abweichung (calcChecksum in
      // CapgoUpdater.swift). Ein abgebrochener Download oder eine halb
      // gespiegelte Datei fällt damit auf, bevor die App sie startet.
      checksum: buendel.sha256,
    })
  } catch (fehler) {
    throw new OtaFehler(
      `Das Update konnte nicht geladen werden (${fehler instanceof Error ? fehler.message : String(fehler)}).`,
    )
  }

  try {
    await CapacitorUpdater.set({ id: geladen.id })
  } catch (fehler) {
    throw new OtaFehler(
      `Das Update wurde geladen, ließ sich aber nicht starten (${fehler instanceof Error ? fehler.message : String(fehler)}).`,
    )
  }
  // Unerreichbar: set() lädt die App neu. Steht hier, damit der Rückgabetyp
  // ehrlich bleibt.
  throw new OtaFehler("Die App hätte an dieser Stelle neu starten müssen.")
}
