/**
 * Ein Zugang zu allen Daten für die gesamte Oberfläche.
 *
 * Hält die vier Datentöpfe (Profil, Fahrten, Tankvorgänge, Fahrzeugstatus),
 * lauscht über die WebSocket-Verbindung auf Änderungen und reicht den
 * Verbindungszustand sowie die Warteschlange nach außen. Die Screens kennen
 * weder REST noch WebSocket — sie lesen aus diesem Kontext.
 */

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react"

import {
  ApiFehler,
  DataMetricApi,
  ENTITAETEN,
  type Buendelangabe,
  type Fahrt,
  type Fahrzeugstatus,
  type IntegrationUpdateAngabe,
  type Profil,
  type Tankvorgang,
  type Verbindungszustand,
  type WartenderAuftrag,
} from "../api"
import type { Versionsstand } from "./appVersion"
import { eigeneVersion, versionVergleichen } from "./appVersion"
import type { Einstellungen, Fahrzeug } from "./profilAdapter"
import { profilZuEinstellungen, profilZuFahrzeug, zusammenfuehren } from "./profilAdapter"

export interface DatenWert {
  bereit: boolean
  /** Erste Ladung fehlgeschlagen und noch nie Daten gesehen. */
  ladefehler: string | null
  verbindung: Verbindungszustand
  warteschlange: readonly WartenderAuftrag[]
  /** Zeitpunkt der letzten Statusmeldung — treibt die Veraltet-Anzeige. */
  statusStand: string | null
  /** Ob diese App noch dem Stand entspricht, den das Backend ausliefert.
      Siehe appVersion.ts und VERSIONIERUNG.md. */
  versionsstand: Versionsstand
  /** Das OTA-Bündel, das die Integration mitliefert (siehe daten/ota.ts),
      oder null — kein Fehler, sondern der Zustand ohne Bündel oder ohne
      Verbindung. */
  otaBuendel: Buendelangabe | null
  /** Ergebnis der letzten Selbst-Update-Prüfung/-Installation der
      Integration (aktualisierung.py), oder null vor der ersten Prüfung. */
  integrationUpdate: IntegrationUpdateAngabe | null

  einstellungen: Einstellungen | null
  fahrzeug: Fahrzeug | null
  fahrten: readonly Fahrt[]
  tankvorgaenge: readonly Tankvorgang[]
  rohprofil: Profil | null

  api: DataMetricApi
  neuLaden: () => Promise<void>
  jetztAktualisieren: () => Promise<void>
  /** Speichert geänderte Einstellungen/Fahrzeugdaten als ganzes Profil. */
  profilSpeichern: (aenderung: {
    einstellungen?: Einstellungen
    fahrzeug?: Fahrzeug
  }) => Promise<void>
}

const Kontext = createContext<DatenWert | null>(null)

export function useDaten(): DatenWert {
  const wert = useContext(Kontext)
  if (!wert) throw new Error("useDaten außerhalb von <DatenAnbieter> verwendet")
  return wert
}

export function DatenAnbieter({
  api,
  children,
}: {
  api: DataMetricApi
  children: ReactNode
}) {
  const [rohprofil, setzeRohprofil] = useState<Profil | null>(null)
  const [status, setzeStatus] = useState<Fahrzeugstatus>({})
  const [statusStand, setzeStatusStand] = useState<string | null>(null)
  const [fahrten, setzeFahrten] = useState<readonly Fahrt[]>([])
  const [tankvorgaenge, setzeTankvorgaenge] = useState<readonly Tankvorgang[]>([])
  const [bereit, setzeBereit] = useState(false)
  const [ladefehler, setzeLadefehler] = useState<string | null>(null)
  const [verbindung, setzeVerbindung] = useState<Verbindungszustand>("getrennt")
  const [warteschlange, setzeWarteschlange] = useState<readonly WartenderAuftrag[]>([])
  const [serverVersion, setzeServerVersion] = useState<string | null>(null)
  const [otaBuendel, setzeOtaBuendel] = useState<Buendelangabe | null>(null)
  const [integrationUpdate, setzeIntegrationUpdate] = useState<IntegrationUpdateAngabe | null>(
    null,
  )

  // Damit profilSpeichern immer gegen den neuesten Rohstand arbeitet, auch
  // wenn zwischendurch ein Push hereinkam.
  const rohprofilRef = useRef<Profil | null>(null)
  rohprofilRef.current = rohprofil

  const neuLaden = useCallback(async () => {
    try {
      const [p, f, t, s] = await Promise.all([
        api.profilLesen(),
        api.fahrtenLesen(),
        api.tankvorgaengeLesen(),
        api.rest.zustandLesen<{ daten: Fahrzeugstatus }>(ENTITAETEN.fahrzeugstatus),
      ])
      setzeRohprofil(p)
      setzeFahrten(f ?? [])
      setzeTankvorgaenge(t ?? [])
      setzeStatus(s.attributes.daten ?? {})
      setzeStatusStand(s.last_updated)
      setzeLadefehler(null)
      setzeBereit(true)
      // Bewusst außerhalb des Promise.all oben und ohne eigenes catch hier:
      // appVersionAngabeLesen() schluckt seine Fehler selbst und liefert dann
      // null. Ein fehlender Versionsvergleich darf die Erstladung nie
      // aufhalten - er ist ein Hinweis, keine Betriebsvoraussetzung.
      const versionsangabe = await api.appVersionAngabeLesen()
      setzeServerVersion(versionsangabe?.app ?? null)
      setzeOtaBuendel(versionsangabe?.buendel ?? null)
      setzeIntegrationUpdate(versionsangabe?.integration_update ?? null)
    } catch (fehler) {
      // Kein Netz ist kein Ladefehler, solange schon Daten da sind — dann
      // zeigt die App den zwischengespeicherten Stand mit Offline-Hinweis.
      const netzproblem = fehler instanceof ApiFehler && fehler.istNetzproblem
      if (!netzproblem || !bereit) {
        setzeLadefehler(fehler instanceof Error ? fehler.message : String(fehler))
      }
    }
  }, [api, bereit])

  // Erstladung plus Live-Verbindung.
  useEffect(() => {
    void neuLaden()
    void api.warteschlange.laden()

    const abVerbindung = api.live.aufVerbindung((zustand) => {
      setzeVerbindung(zustand)
      // Nach einem Wiederverbinden kann während der Trennung etwas passiert
      // sein, das kein Ereignis mehr ausgelöst hat.
      if (zustand === "verbunden") void neuLaden()
    })

    const abZustand = api.live.aufZustand(({ entity_id, new_state }) => {
      if (!new_state) return
      const daten = (new_state.attributes as { daten?: unknown }).daten
      switch (entity_id) {
        case ENTITAETEN.profil:
          setzeRohprofil((daten ?? null) as Profil | null)
          break
        case ENTITAETEN.fahrten:
          setzeFahrten((daten ?? []) as Fahrt[])
          break
        case ENTITAETEN.tankvorgaenge:
          setzeTankvorgaenge((daten ?? []) as Tankvorgang[])
          break
        case ENTITAETEN.fahrzeugstatus:
          setzeStatus((daten ?? {}) as Fahrzeugstatus)
          setzeStatusStand(new_state.last_updated)
          break
        default:
          break
      }
    })

    const abWarteschlange = api.warteschlange.aufAenderung(setzeWarteschlange)
    void api.live.starten()

    return () => {
      abVerbindung()
      abZustand()
      abWarteschlange()
      api.live.beenden()
    }
    // Absichtlich nur an `api` gebunden: neuLaden ändert sich mit `bereit`,
    // was hier sonst die Verbindung bei jeder Erstladung neu aufbauen würde.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [api])

  const einstellungen = useMemo(
    () => (rohprofil ? profilZuEinstellungen(rohprofil) : null),
    [rohprofil],
  )
  const fahrzeug = useMemo(
    () => (rohprofil ? profilZuFahrzeug(rohprofil, status) : null),
    [rohprofil, status],
  )

  const profilSpeichern = useCallback(
    async (aenderung: { einstellungen?: Einstellungen; fahrzeug?: Fahrzeug }) => {
      const roh = rohprofilRef.current
      if (!roh) throw new Error("Kein Profil geladen — Speichern nicht möglich.")
      const neu = zusammenfuehren(
        roh,
        aenderung.einstellungen ?? profilZuEinstellungen(roh),
        aenderung.fahrzeug ?? profilZuFahrzeug(roh, status),
      )
      // Sofort lokal übernehmen, damit die Oberfläche nicht auf den
      // Rückweg über das Backend warten muss (die Warteschlange kann im
      // Funkloch beliebig lange brauchen).
      setzeRohprofil(neu)
      await api.profilSchreiben(neu)
    },
    [api, status],
  )

  const jetztAktualisieren = useCallback(async () => {
    await api.jetztAktualisieren()
    await neuLaden()
  }, [api, neuLaden])

  const wert: DatenWert = {
    bereit,
    ladefehler,
    verbindung,
    warteschlange,
    statusStand,
    versionsstand: versionVergleichen(eigeneVersion(), serverVersion).stand,
    otaBuendel,
    integrationUpdate,
    einstellungen,
    fahrzeug,
    fahrten,
    tankvorgaenge,
    rohprofil,
    api,
    neuLaden,
    jetztAktualisieren,
    profilSpeichern,
  }

  return <Kontext.Provider value={wert}>{children}</Kontext.Provider>
}
