/**
 * Protection orchestration layer (JS-side).
 *
 * Verbindet das native rebreak-protection-Modul (Device-Layer-State) mit
 * dem Backend-Cooldown-API (`/api/cooldown/*` + `/api/protection/state`).
 *
 * Cooldown ist Backend-driven (JWT mit `cooldown_ends_at`-Claim, server-time
 * = single source of truth gegen lokale-Uhr-Manipulation). Native-Modul
 * kümmert sich nur um echten Device-State (NEFilter, Family Controls etc.).
 */
import { Platform } from "react-native";
import Constants from "expo-constants";
import AsyncStorage from "@react-native-async-storage/async-storage";
import RebreakProtection from "../modules/rebreak-protection";
import type {
  ActivateResult,
  DeviceLayers,
  HealthProbeOpts,
  HealthProbeResult,
  SyncBlocklistOpts,
  SyncBlocklistResult,
  SyncWebContentDomainsOpts,
  SyncWebContentDomainsResult,
  SystemSettingsTarget,
  WebContentFilterResult,
} from "../modules/rebreak-protection";
import { apiFetch } from "./api";

// ─── Feature Flags ─────────────────────────────────────────────────────────

/**
 * True only in dev/dev-client builds where Apple's FamilyControls Development
 * entitlement is active. False in preview/production (Distribution entitlement
 * pending Apple approval) — showing the App-Lock toggle in those builds would
 * throw NSCocoaErrorDomain:4099.
 */
export const FAMILY_CONTROLS_AVAILABLE =
  Constants.expoConfig?.extra?.familyControlsEnabled === true;

// ─── Public Types ──────────────────────────────────────────────────────────

export type ProtectionPhase =
  | "inactive"
  | "activating"
  | "active"
  | "cooldownPending"
  | "cooldownActive"
  | "recoveringFromBypass";

export type CooldownState = {
  active: boolean;
  endsAt: string | null;
  remainingSeconds: number;
  reason: string | null;
};

export type ProtectionState = {
  phase: ProtectionPhase;
  layers: DeviceLayers;
  cooldown: CooldownState;
  blocklistCount: number;
  plan: "free" | "pro" | "legend";
  /** Backend-reported: true wenn das Gerät als MDM-managed markiert wurde. */
  mdmManaged: boolean;
};

// ─── Backend Response-Types ────────────────────────────────────────────────

type BackendCooldownStatus = {
  active: boolean;
  remainingSeconds: number;
  cooldownEndsAt: string | null;
  token: string | null;
  canDisableProtection: boolean;
  reason?: string;
};

// Matches actual response from `apps/rebreak/server/api/protection/state.get.ts`
// (apiFetch unwrapt das `data`-Feld bereits, daher hier nur die Inner-Shape).
type BackendProtectionState = {
  protectionShouldBeActive: boolean;
  cooldown: {
    active: boolean;
    remainingSeconds: number;
    cooldownEndsAt: string | null;
  };
  plan: "free" | "pro" | "legend";
  mdmManaged: boolean;
};

// ─── Dev Helpers ───────────────────────────────────────────────────────────

const DEV_COOLDOWN_TESTMODE_KEY = "dev:cooldown-testmode";

export async function setCooldownTestMode(on: boolean): Promise<void> {
  await AsyncStorage.setItem(DEV_COOLDOWN_TESTMODE_KEY, on ? "1" : "0");
}

export async function getCooldownTestMode(): Promise<boolean> {
  const val = await AsyncStorage.getItem(DEV_COOLDOWN_TESTMODE_KEY);
  return val === "1";
}

// ─── Per-Device "war hier schon mal aktiv"-Flag ─────────────────────────────
// `protectionShouldBeActive` (Backend) ist der DEFAULT jedes Accounts: true,
// solange kein Cooldown durchgehalten wurde. Es bedeutet NICHT "Schutz war
// jemals auf DIESEM Gerät aktiv". Ohne dieses lokale Flag würde
// `recoveringFromBypass` auf jedem frischen Gerät/Sim sofort feuern (→ Bypass-
// Push + blockierendes ProtectionOffSheet), obwohl hier nie Schutz lief. Das
// Flag wird nach erfolgreichem activateUrlFilter() gesetzt und gated den
// Bypass-Zustand in getCombinedState().
const PROTECTION_EVER_ACTIVE_KEY = "protection:everActiveHere";

async function markProtectionActivatedHere(): Promise<void> {
  await AsyncStorage.setItem(PROTECTION_EVER_ACTIVE_KEY, "1").catch(() => {});
}

async function wasProtectionEverActiveHere(): Promise<boolean> {
  const val = await AsyncStorage.getItem(PROTECTION_EVER_ACTIVE_KEY).catch(
    () => null,
  );
  return val === "1";
}

// ─── Public API ────────────────────────────────────────────────────────────

export const protection = {
  // ─── Native-Calls (Device-Layer) ─────────────────────────────────────────

  activate(): Promise<ActivateResult> {
    return RebreakProtection.activate();
  },

  /**
   * iOS-only: read-only check ob NEFilter aktiv ist (egal ob via App-Code
   * oder via Sideload-Profile). Build 19 (2026-05-26): primary state-source
   * für blocker.tsx — wenn enabled=true → UI all-green, kein Schutz-Activate-Button.
   */
  async isNeFilterActive(): Promise<{
    enabled: boolean;
    localizedDescription?: string;
    error?: string;
  }> {
    if (Platform.OS !== "ios") return { enabled: false };
    try {
      return await RebreakProtection.isNeFilterActive();
    } catch (e) {
      return { enabled: false, error: String(e) };
    }
  },

  /**
   * iOS-only: probiert NEFilterDataProvider-Setup und retourniert ob's
   * funktioniert. Wenn enabled=true → Device ist MDM-managed (auto-Toggle on).
   * Wenn enabled=false → Device kann NEFilter nicht (iOS-Wall) → VPN-Pfad.
   * Setzt KEIN Flag selbst — Caller (Settings-UI) ruft setMdmSupervised().
   */
  async probeContentFilter(): Promise<{ enabled: boolean; error?: string }> {
    if (Platform.OS !== "ios") {
      return { enabled: false, error: "ios_only" };
    }
    const res = await RebreakProtection.probeContentFilter();
    const resAny = res as Record<string, unknown>;
    const nativeLog = resAny.log;
    delete resAny.log;
    console.log(`[protection] probeContentFilter → ${JSON.stringify(res)}`);
    if (Array.isArray(nativeLog)) {
      for (const l of nativeLog) console.log(`  [native] ${l}`);
    }
    return res;
  },

  async activateUrlFilter(): Promise<{ enabled: boolean; error?: string }> {
    let res: { enabled: boolean; error?: string };
    if (Platform.OS === "android") {
      // Android Layer-1 = VpnService (DNS-Filter). iOS-API erwartet hier
      // {enabled, error?}, also Native-`activate()`-Result re-shapen.
      const r = await RebreakProtection.activate();
      const enabled = !r.missingLayers.includes("vpn");
      res = enabled ? { enabled: true } : { enabled: false, error: r.errors?.[0] };
    } else {
      // iOS Layer-1: PacketTunnel-VPN-Pfad. NEFilter läuft via sideloaded/MDM-
      // Profil ohne App-Code-Intervention — App aktiviert es nie selbst.
      // activateUrlFilter() ist nur noch für PacketTunnel (VPN) relevant.
      const pirServerURL = (Constants.expoConfig?.extra?.pirServerURL as string) ?? "";
      const pirAuthToken = (Constants.expoConfig?.extra?.pirAuthToken as string) ?? "";
      res = await RebreakProtection.activateUrlFilter({
        pirServerURL,
        pirAuthToken,
        supervised: false,
      });
    }
    // Diagnose: Fehler-String + nativer Log-Tail (inkl. der [EXT ...]-Zeilen
    // der Control-Provider-Extension) zeilenweise in Metro.
    {
      const resAny = res as Record<string, unknown>;
      const nativeLog = resAny.log;
      delete resAny.log;
      console.log(`[protection] activateUrlFilter → ${JSON.stringify(res)}`);
      if (Array.isArray(nativeLog)) {
        for (const l of nativeLog) console.log(`  [native] ${l}`);
      }
    }
    // Bei erfolgreicher Reaktivierung: Backend-Flag clearen (sonst bleibt
    // protectionShouldBeActive=false und Bypass-Detection feuert nicht mehr).
    // Best-effort — wenn das Backend nicht erreichbar ist, lokal nicht blocken.
    if (res.enabled) {
      // Lokales "war hier schon mal aktiv"-Signal — gated recoveringFromBypass
      // (siehe getCombinedState), damit Bypass-Push/Sheet nicht auf frischen
      // Geräten feuern, wo der Schutz nie lief.
      void markProtectionActivatedHere();
      apiFetch("/api/protection/mark-active", { method: "POST" }).catch(() => {});
    }
    return res;
  },

  /**
   * iOS-only Workaround: User hat "Nicht erlauben" beim NEFilter-System-Dialog
   * getippt → iOS cached den Denied-State + zeigt den Dialog beim erneuten
   * activateUrlFilter NICHT mehr (NEFilterErrorDomain code 5 silent).
   * resetUrlFilter macht removeFromPreferences VOR saveToPreferences — iOS
   * behandelt das als frischen Request → System-Dialog kommt erneut.
   *
   * Auf Android no-op (gibt einfach activateUrlFilter zurück, dort kein Cache).
   */
  async resetUrlFilter(): Promise<{ enabled: boolean; error?: string }> {
    if (Platform.OS === "android") {
      return this.activateUrlFilter();
    }
    return RebreakProtection.resetUrlFilter();
  },

  async activateFamilyControls(): Promise<{ enabled: boolean; error?: string }> {
    if (Platform.OS === "android") {
      // Android "App-Lock" = AccessibilityService als reiner Tamper-Lock (KEIN
      // Browser-Filter mehr — Glücksspielseiten blockt der VpnService DNS-Filter).
      // Der a11y-Service verhindert nur, dass schutz-relevante Settings geöffnet
      // werden (VPN abschalten / App deinstallieren / a11y-Service abschalten).
      // Two-step UX:
      //   (1) A11y nicht aktiv → Settings öffnen, return {enabled:false} mit
      //       Marker-Error. UI fragt nach Return den State neu ab und tappt
      //       erneut auf "App lock" → wir landen in Step (2).
      //   (2) A11y aktiv → tamperLock armen → return {enabled:true}.
      const a11y = await RebreakProtection.isAccessibilityEnabled();
      if (!a11y.enabled) {
        // Deep-Link direkt zu ReBreaks a11y-Detail-Page (5-stufige Fallback-Kette,
        // Samsung-Highlight). Der Overlay-Gatekeeper, der das früher blockte, ist
        // nativ entfernt → landet jetzt direkt am ReBreak-Schalter statt auf der
        // Overlay-Permission-Seite. (Braucht den nativen Rebuild zum Greifen.)
        await RebreakProtection.openAccessibilitySettings();
        return { enabled: false, error: "accessibility_pending" };
      }
      try {
        await RebreakProtection.armTamperLock();
        return { enabled: true };
      } catch (e: any) {
        return {
          enabled: false,
          error: e?.message ?? "tamper_lock_failed",
        };
      }
    }
    return RebreakProtection.activateFamilyControls();
  },

  /**
   * Android-only: löst den System-Dialog zum Aktivieren des Geräteadministrators aus.
   * Gibt {launched:true} zurück wenn der Dialog gestartet wurde. Das tatsächliche
   * Ergebnis (accept/deny) liest die UI via AppState-Return + `isDeviceAdminActive`.
   */
  async requestDeviceAdmin(): Promise<{ launched: boolean }> {
    if (Platform.OS !== "android") return { launched: false };
    try {
      return await RebreakProtection.requestDeviceAdmin();
    } catch (e) {
      console.warn("[protection] requestDeviceAdmin failed:", e);
      return { launched: false };
    }
  },

  /** Android-only: Deaktiviert den Device-Admin-Receiver. Wird im Cooldown-Resolve aufgerufen. */
  async removeDeviceAdmin(): Promise<void> {
    if (Platform.OS !== "android") return;
    try {
      await RebreakProtection.removeDeviceAdmin();
    } catch (e) {
      console.warn("[protection] removeDeviceAdmin failed:", e);
    }
  },

  /** Schaltet alle Layer ab + disarmed den Tamper-Lock. NUR aufrufen wenn JS-Layer Cooldown verifiziert. */
  async forceDisable() {
    console.log("[protection] forceDisable() — disarm tamper + native disable");
    // Tamper-Lock ZUERST disarmen — sonst setzt der AccessibilityService den Schutz
    // nach dem Cooldown weiter durch (blockt z.B. das Ausschalten des a11y-Service in den
    // System-Settings) → der User kommt nicht aus dem Schutz raus, obwohl der Cooldown
    // abgelaufen ist. (Android-Bug-Fix.)
    try {
      await RebreakProtection.disarmTamperLock();
    } catch (e) {
      console.warn("[protection] disarmTamperLock failed:", e);
    }
    // Device-Admin MUSS vor disable() entfernt werden — sonst kann die App nach dem
    // Cooldown nicht deinstalliert werden (aktiver Device-Admin blockt Deinstallation).
    // Das ist die Safety-Auflage: legitimer Ausstieg via Cooldown MUSS funktionieren.
    if (Platform.OS === "android") {
      try {
        await RebreakProtection.removeDeviceAdmin();
      } catch (e) {
        console.warn("[protection] removeDeviceAdmin in forceDisable failed:", e);
      }
    }
    const res = await RebreakProtection.disable();
    console.log("[protection] native disable returned:", res);
    return res;
  },

  getDeviceState(): Promise<DeviceLayers> {
    return RebreakProtection.getDeviceState();
  },

  // ─── Android: Akku-Ausnahme (gegen Samsung-Sleep, der den a11y-Lock killt) ──
  //
  // Ohne Battery-Exemption schläfert Samsung & Co. die App ein → der a11y-
  // Service wird entbunden → Tamper-Lock erzwingt nichts mehr (Schutz fällt
  // still aus). Daher Status + Anforderung exponieren.

  /** Android: Ist die App von der Akku-Optimierung ausgenommen? (iOS: immer true) */
  async isBatteryOptimizationIgnored(): Promise<boolean> {
    if (Platform.OS !== "android") return true;
    try {
      const r = await RebreakProtection.isBatteryOptimizationIgnored();
      return r.ignored === true;
    } catch (e) {
      console.warn("[protection] isBatteryOptimizationIgnored failed:", e);
      return false;
    }
  },

  /** Android: System-Dialog „Akku-Optimierung ignorieren?" (ein Tap „Zulassen"). */
  async requestIgnoreBatteryOptimizations() {
    if (Platform.OS !== "android")
      return { opened: false, alreadyIgnored: true } as {
        opened: boolean;
        alreadyIgnored?: boolean;
      };
    return RebreakProtection.requestIgnoreBatteryOptimizations();
  },

  /** Android: App-Detail-Settings öffnen — Samsung: Akku „Uneingeschränkt" +
   *  raus aus „Schlafende/Tief schlafende Apps". */
  async openAppDetailsSettings() {
    if (Platform.OS !== "android") return { opened: false };
    return RebreakProtection.openAppDetailsSettings();
  },

  // ─── iOS Layer 2 — webContent-Filter (ManagedSettings) ───────────────────
  //
  // Stilles WebKit-Sicherheitsnetz: blockt eine kuratierte, länderabhängige
  // Top-Gambling-Domain-Liste (≤50 Domains — Apple-Hartlimit) via
  // ManagedSettings. Braucht NUR Family Controls (kein MDM, kein neues
  // Entitlement). Auf Android/Web no-op.
  //
  // TODO(layer2-gating): Aktuell NUR explizit aufrufbare Capability. Die
  // Auto-Trigger-Logik ("Layer 2 automatisch AN sobald NEURLFilter/Layer 1
  // aus ist + ein Cooldown läuft, sonst AUS") ist bewusst NICHT gebaut — sie
  // hängt an einer zuverlässigen NEURLFilter-Status-Erkennung (die aktuell
  // selbst nicht stabil funktioniert) und ist eine offene Produkt-/Design-
  // Entscheidung des Users. Bis dahin müssen Aufrufer applyWebContentFilter /
  // clearWebContentFilter explizit triggern.

  /**
   * Aktiviert Layer 2 — der webContent-Filter blockt die gebündelte Top-
   * Gambling-Domain-Liste des Geräte-Landes. Setzt eine aktive Family-Controls-
   * Authorization voraus. No-op auf Android/Web (gibt enabled:false zurück).
   */
  async applyWebContentFilter(): Promise<WebContentFilterResult> {
    if (Platform.OS !== "ios") {
      return { enabled: false, appliedCount: 0, region: "", error: "ios_only" };
    }
    return RebreakProtection.applyWebContentFilter();
  },

  /**
   * Setzt Layer 2 zurück (blockedByFilter = .none). Rührt den App-Lock
   * (denyAppRemoval) NICHT an. No-op auf Android/Web.
   */
  async clearWebContentFilter(): Promise<{ cleared: boolean; error?: string }> {
    if (Platform.OS !== "ios") {
      return { cleared: false, error: "ios_only" };
    }
    return RebreakProtection.clearWebContentFilter();
  },

  /**
   * Synct die kuratierte Layer-2-Gambling-Domain-Liste vom Backend in den
   * App-Group-Cache (`webcontent-domains.json`). Die gebündelte JSON bleibt
   * Offline-Seed/Fallback. Nach erfolgreichem Sync wird der webContent-Filter
   * — wenn Family Controls authorisiert ist — nativ sofort reapplied.
   *
   * No-op auf Android/Web (Layer 2 ist iOS-only) → gibt updated:false zurück.
   * Best-effort: solange der Backend-Endpoint nicht deployed ist, schlägt der
   * Fetch fehl — der native loadWebContentDomains fällt dann sauber auf die
   * gebündelte JSON zurück. Der Aufrufer behandelt einen Fehler als nicht-fatal.
   */
  async syncWebContentDomains(
    opts: SyncWebContentDomainsOpts,
  ): Promise<SyncWebContentDomainsResult> {
    if (Platform.OS !== "ios") {
      return { updated: false };
    }
    return RebreakProtection.syncWebContentDomains(opts);
  },

  /** Self-Heal Layer-1-Filter. Bei App-Start/Foreground/Poll aufrufen.
   *
   *  Android: VpnService neu starten falls er laufen sollte (`filter_enabled`)
   *           aber tot ist (Reinstall / OS-Kill).
   *  iOS:     prüft ob unser NETunnelProviderManager noch da ist; falls User
   *           „VPN löschen" in Settings getippt hat → silent recreate
   *           (loadOrCreateTunnelManager + saveToPreferences + startVPNTunnel).
   *           Wenn iOS Permission-Dialog zeigt: akzeptierte Friktion.
   *  MDM-Mode (iOS): NEFilter läuft via Sideload-/MDM-Profile autonom — App-
   *           Code darf den VPN-Stack nicht reaktivieren (würde Permissions-Dialog
   *           triggern). Wenn NEFilter aktiv → no-op.
   *  Web:     no-op.
   */
  async reconcileVpn(): Promise<void> {
    if (Platform.OS === "ios") {
      const nef = await this.isNeFilterActive().catch(() => ({ enabled: false }));
      if (nef.enabled) return;
      try {
        const res = await RebreakProtection.reconcileUrlFilter();
        if (res?.recreated) {
          console.log("[protection] iOS Packet-Tunnel auto-recreated nach VPN-Delete");
        } else if (res?.error) {
          console.warn(`[protection] reconcileUrlFilter (ios) error: ${res.error}`);
        }
      } catch (e) {
        console.warn("[protection] reconcileUrlFilter (ios) failed:", e);
      }
      return;
    }
    if (Platform.OS === "android") {
      try {
        await RebreakProtection.reconcileVpn();
      } catch (e) {
        console.warn("[protection] reconcileVpn (android) failed:", e);
      }
      return;
    }
  },

  syncBlocklist(opts: SyncBlocklistOpts): Promise<SyncBlocklistResult> {
    return RebreakProtection.syncBlocklist(opts);
  },

  runHealthProbe(opts?: HealthProbeOpts): Promise<HealthProbeResult> {
    return RebreakProtection.runHealthProbe(opts);
  },

  /** Android: Hat die App Nutzungszugriff (für den state-aware a11y-Guide)? */
  async hasUsageAccess(): Promise<boolean> {
    if (Platform.OS !== "android") return false;
    try {
      const r = await RebreakProtection.hasUsageAccess();
      return r?.granted === true;
    } catch {
      return false;
    }
  },

  /** Android: Öffnet die Nutzungszugriff-Settings zum Freigeben. */
  async openUsageAccessSettings(): Promise<void> {
    if (Platform.OS !== "android") return;
    try {
      await RebreakProtection.openUsageAccessSettings();
    } catch (e) {
      console.warn("[protection] openUsageAccessSettings failed:", e);
    }
  },

  /** Android: Hat die App „Über anderen Apps anzeigen" (passives Guide-Overlay)? */
  async hasOverlayPermission(): Promise<boolean> {
    if (Platform.OS !== "android") return false;
    try {
      const r = await RebreakProtection.hasOverlayPermission();
      return r?.granted === true;
    } catch {
      return false;
    }
  },

  /** Android: Öffnet die „Über anderen Apps anzeigen"-Settings zum Freigeben. */
  async openOverlayPermissionSettings(): Promise<void> {
    if (Platform.OS !== "android") return;
    try {
      await RebreakProtection.openOverlayPermissionSettings();
    } catch (e) {
      console.warn("[protection] openOverlayPermissionSettings failed:", e);
    }
  },

  openSystemSettings(target?: SystemSettingsTarget): Promise<void> {
    return RebreakProtection.openSystemSettings(target);
  },

  /** iOS: native Protection-Logs (NEFilter/FamilyControls flow) für Debug-Page.
   *  Auf Android/Web no-op → leeres Array. */
  async getProtectionLogs(): Promise<string[]> {
    if (Platform.OS !== "ios") return [];
    try {
      return await RebreakProtection.getProtectionLogs();
    } catch {
      return [];
    }
  },

  /** iOS: leert die nativen Protection-Logs. No-op auf Android/Web. */
  async clearProtectionLogs(): Promise<void> {
    if (Platform.OS !== "ios") return;
    try {
      await RebreakProtection.clearProtectionLogs();
    } catch {
      // ignore
    }
  },

  addLayerChangeListener(cb: (layers: DeviceLayers) => void) {
    return RebreakProtection.addListener("onLayerChange", cb);
  },

  // ─── Backend-Cooldown ────────────────────────────────────────────────────

  /** Startet 24h Cooldown (oder 40s bei aktivem __DEV__-testMode). Schutz BLEIBT aktiv. */
  async requestDeactivation(
    reason?: string,
  ): Promise<{ cooldownEndsAt: string }> {
    const testMode = __DEV__ ? await getCooldownTestMode() : false;
    const body: Record<string, unknown> = { reason };
    if (testMode) body.testMode = true;
    const res = await apiFetch<{
      cooldownEndsAt: string;
      token: string;
      remainingSeconds: number;
    }>("/api/cooldown/request", { method: "POST", body });
    return { cooldownEndsAt: res.cooldownEndsAt };
  },

  // ─── Screen Time Passcode (iOS Layer 3) ────────────────────────────────

  /** Speichert den generierten 4-stelligen Screen-Time-Passcode auf dem Backend.
   *  Wird im Onboarding aufgerufen nachdem User bestätigt hat den Code gesetzt zu haben. */
  async saveScreenTimePasscode(passcode: string): Promise<void> {
    await apiFetch("/api/protection/screentime-passcode", {
      method: "POST",
      body: { passcode },
    });
  },

  /** Ruft den Screen-Time-Passcode ab — nur wenn kein aktiver Cooldown läuft.
   *  Gibt null zurück wenn Code fehlt oder Cooldown noch aktiv ist. */
  async getScreenTimePasscode(): Promise<string | null> {
    const res = await apiFetch<{ passcode: string | null }>(
      "/api/protection/screentime-passcode",
    );
    return res.passcode;
  },

  /** Bricht laufenden Cooldown ab. Schutz BLEIBT aktiv. */
  async cancelDeactivation(): Promise<{ cancelled: boolean }> {
    const res = await apiFetch<{ cancelled: boolean }>("/api/cooldown/cancel", {
      method: "POST",
      body: {},
    });
    return res;
  },

  async getCooldownStatus(): Promise<CooldownState> {
    try {
      const res = await apiFetch<BackendCooldownStatus>("/api/cooldown/status");
      return {
        active: res.active,
        endsAt: res.cooldownEndsAt,
        remainingSeconds: res.remainingSeconds,
        reason: res.reason ?? null,
      };
    } catch {
      // Offline / Backend down → konservativ: kein Cooldown angenommen
      return { active: false, endsAt: null, remainingSeconds: 0, reason: null };
    }
  },

  async getBackendProtectionState(): Promise<BackendProtectionState | null> {
    try {
      return await apiFetch<BackendProtectionState>("/api/protection/state");
    } catch {
      return null;
    }
  },

  // ─── Combined State (für UI) ─────────────────────────────────────────────

  /**
   * Holt nativen Device-State + Backend-Cooldown parallel und merged.
   * Phase-Berechnung folgt der State-Machine im Plan.
   */
  async getCombinedState(): Promise<ProtectionState> {
    const [rawLayers, cooldown, backend, nefilterRes, everActiveHere] = await Promise.all([
      this.getDeviceState(),
      this.getCooldownStatus(),
      this.getBackendProtectionState(),
      Platform.OS === "ios" ? this.isNeFilterActive() : Promise.resolve({ enabled: false }),
      wasProtectionEverActiveHere(),
    ]);

    // Android's native module reports {vpn, accessibility, tamperLock}; the UI
    // (blocker.tsx, isAllLayersOn) reads the iOS-shaped names {urlFilter,
    // familyControls, appDeletionLock}. Alias them so consumers are platform-
    // agnostic. Android "App-Lock" = AccessibilityService + armed tamper-lock,
    // so the lock-state maps to `tamperLock`.
    const layersBase: DeviceLayers =
      Platform.OS === "android" && rawLayers.urlFilter === undefined
        ? ({
            ...rawLayers,
            urlFilter: rawLayers.vpn,
            // App-Lock gilt NUR als aktiv, wenn der a11y-Service WIRKLICH läuft —
            // nicht nur weil `tamper_armed` (Pref) gesetzt ist. Sonst zeigt die UI
            // "komplett geschützt", obwohl a11y (z.B. nach Reboot von Samsung
            // deaktiviert; programmatisch nicht reaktivierbar) nichts mehr erzwingt
            // → falsches Sicherheitsgefühl. (Analog zum bestehenden VPN-flag-Gate
            // in der nativen tamperLock-Berechnung.)
            familyControls:
              rawLayers.tamperLock === true && rawLayers.accessibility === true,
            appDeletionLock:
              rawLayers.tamperLock === true && rawLayers.accessibility === true,
          } as DeviceLayers)
        : rawLayers;

    const layers: DeviceLayers = {
      ...layersBase,
      nefilterActive: nefilterRes.enabled,
      nefilterDescription: (nefilterRes as { enabled: boolean; localizedDescription?: string }).localizedDescription,
    };

    // "Aktiv" = der eigentliche Schutz läuft. Entweder via PacketTunnel-VPN
    // (urlFilter=true) ODER via System-/MDM-Profil-NEFilter (nefilterActive=true).
    // MDM-Mode: NEFilter läuft autonom — App-Code hat urlFilter=false (kein VPN),
    // aber nefilterActive=true. Beide gelten als aktiver Schutz.
    // "recoveringFromBypass" AUSSCHLIESSLICH wenn: der Schutz auf DIESEM Gerät
    // schon einmal lokal aktiv war (everActiveHere) UND Backend sagt Schutz soll
    // aktiv sein UND weder VPN noch NEFilter laufen UND wir nicht MDM-managed
    // sind. Ohne everActiveHere ist "shouldBeActive + kein Filter" nur der
    // normale Erst-Setup-/Aus-Zustand (Account-Default shouldBeActive=true) →
    // `inactive` (Setup-CTA), KEIN Bypass-Push/Sheet.
    const filterActive = layers.urlFilter === true || layers.nefilterActive === true;
    const phase: ProtectionPhase = cooldown.active
      ? "cooldownActive"
      : filterActive
        ? "active"
        : everActiveHere &&
            backend?.protectionShouldBeActive === true &&
            !backend?.mdmManaged
          ? "recoveringFromBypass"
          : "inactive";

    return {
      phase,
      layers,
      cooldown,
      blocklistCount: layers.blocklistCount,
      plan: backend?.plan ?? "free",
      mdmManaged: backend?.mdmManaged ?? false,
    };
  },

  /**
   * Wenn ein Cooldown TATSÄCHLICH gelaufen ist und jetzt elapsed → native disable.
   *
   * Defensiv: prüft `cooldownEndsAt` (heißt es gab einen Cooldown) UND
   * `remainingSeconds <= 0` (heißt er ist abgelaufen) UND `canDisableProtection`.
   * Backend kann `canDisableProtection: true` auch im initial-state geben;
   * der `cooldownEndsAt`-Check verhindert dann False-Positives.
   */
  async applyCooldownDisableIfElapsed(): Promise<boolean> {
    const status = await apiFetch<BackendCooldownStatus>(
      "/api/cooldown/status",
    ).catch(() => null);
    if (!status) return false;
    if (!status.canDisableProtection) return false;
    if (!status.cooldownEndsAt) return false; // nie ein Cooldown gewesen
    if (status.remainingSeconds > 0) return false; // Cooldown noch nicht abgelaufen
    await this.forceDisable();
    return true;
  },
};

// ─── Helpers ───────────────────────────────────────────────────────────────

export function isAllLayersOn(layers: DeviceLayers): boolean {
  // iOS: urlFilter + appDeletionLock (fallback: familyControls für ältere Builds).
  // Android: vpn + accessibility (+ tamperLock optional).
  if (
    layers.urlFilter !== undefined ||
    layers.familyControls !== undefined ||
    layers.appDeletionLock !== undefined
  ) {
    const lockLayer = layers.appDeletionLock ?? layers.familyControls;
    return layers.urlFilter === true && lockLayer === true;
  }
  if (layers.vpn !== undefined || layers.accessibility !== undefined) {
    return layers.vpn === true && layers.accessibility === true;
  }
  return false;
}

export function formatCooldownRemaining(seconds: number): string {
  if (seconds <= 0) return "00:00:00";
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  const s = seconds % 60;
  return [h, m, s].map((n) => String(n).padStart(2, "0")).join(":");
}
