/**
 * 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 RebreakProtection from "../modules/rebreak-protection";
import type {
  ActivateResult,
  DeviceLayers,
  HealthProbeOpts,
  HealthProbeResult,
  SyncBlocklistOpts,
  SyncBlocklistResult,
  SystemSettingsTarget,
} from "../modules/rebreak-protection";
import { apiFetch } from "./api";

// ─── 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 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";
};

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

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

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

  async activateUrlFilter(): Promise<{ 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 res = await RebreakProtection.activate();
      const enabled = !res.missingLayers.includes("vpn");
      return enabled ? { enabled: true } : { enabled: false, error: res.errors?.[0] };
    }
    return RebreakProtection.activateUrlFilter();
  },

  async activateFamilyControls(): Promise<{ enabled: boolean; error?: string }> {
    if (Platform.OS === "android") {
      // Android Layer-2 = AccessibilityService (Browser-URL-Filter) + Tamper-Lock.
      // 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) {
        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();
  },

  /** Schaltet alle Layer ab. NUR aufrufen wenn JS-Layer Cooldown verifiziert. */
  forceDisable() {
    return RebreakProtection.disable();
  },

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

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

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

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

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

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

  /** Startet 24h Cooldown. Schutz BLEIBT aktiv, kann erst nach Ablauf disabled werden. */
  async requestDeactivation(
    reason?: string,
  ): Promise<{ cooldownEndsAt: string }> {
    const res = await apiFetch<{
      cooldownEndsAt: string;
      token: string;
      remainingSeconds: number;
    }>("/api/cooldown/request", { method: "POST", body: { reason } });
    return { cooldownEndsAt: res.cooldownEndsAt };
  },

  /** 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 [layers, cooldown, backend] = await Promise.all([
      this.getDeviceState(),
      this.getCooldownStatus(),
      this.getBackendProtectionState(),
    ]);

    const allLayersOn = isAllLayersOn(layers);
    const iosLockActive =
      layers.appDeletionLock ?? layers.familyControls ?? false;
    const phase: ProtectionPhase = cooldown.active
      ? "cooldownActive"
      : backend?.protectionShouldBeActive === true &&
          layers.urlFilter === true &&
          iosLockActive !== true
        ? "recoveringFromBypass"
        : allLayersOn
          ? "active"
          : "inactive";

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

  /**
   * 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(":");
}
