import { initializeLocalDatabase, deleteInterventionLocally } from "../core/localDatabase";
import { getInterventionById } from "./interventionsApi";

// Chargement PARESSEUX + gardé des modules natifs. `expo-task-manager` est un
// module natif AJOUTÉ récemment : un `import * as TaskManager from
// "expo-task-manager"` STATIQUE en tête de module déclenche son
// `requireNativeModule` AU CHARGEMENT DU BUNDLE. Si ce bundle JS est livré en
// OTA sur un APK qui ne contient pas encore ce module natif (rebuild pas fait),
// ce throw se produit AVANT tout try/catch -> crash au lancement, puis rollback
// expo-updates sur l'ancien bundle. Conséquence vécue : l'app "crashe à la mise
// à jour" ET les correctifs JS purs ne s'appliquent jamais. On require donc à la
// demande, sous try/catch, pour dégrader en silence (couche 2 inactive jusqu'au
// rebuild APK) sans jamais casser le démarrage ni le canal OTA.
function loadTaskManager(): typeof import("expo-task-manager") | null {
  try {
    return require("expo-task-manager");
  } catch {
    return null;
  }
}

function loadNotifications(): typeof import("expo-notifications") | null {
  try {
    return require("expo-notifications");
  } catch {
    return null;
  }
}

/**
 * Couche 2 du pré-cache offline (cf docs/AUDIT-OFFLINE-2026-06-11.md §E).
 *
 * Objectif : pré-cacher le workflow d'une intervention SANS que le technicien
 * ouvre l'app. Le backend (app #177 / coolcare #178) envoie un **data push**
 * (`_contentAvailable:true` + `priority:high`) dont le `data` porte
 * `{ intervention_id, type, action }`. On **keye sur `action`** (`upsert`/
 * `delete`) + `intervention_id`, en IGNORANT `type` (qui reste granulaire côté
 * backend pour le deep-link visible : intervention_created/updated/deleted).
 * La tâche de fond (expo-task-manager + expo-notifications) se réveille même app
 * fermée et pull le détail + les étapes (matérialise serveur + cache local), ou
 * purge en local sur `delete` (suppression OU réassignation : l'ancien tech
 * reçoit `action:delete`).
 *
 * Fiabilité : Android = bon (FCM data haute priorité réveille l'app) ; iOS =
 * best-effort (silent push throttlé par Apple, non délivré si l'app est
 * force-quittée). Dégradation gracieuse : sans data push, la tâche ne se
 * déclenche jamais — rien ne casse.
 *
 * /!\ Module natif (expo-task-manager) + background modes -> REBUILD APK requis,
 * pas d'OTA. La forme exacte du payload reçu en tâche de fond est à VALIDER SUR
 * DEVICE (d'où l'extracteur défensif extractSyncFromData).
 */

export const BACKGROUND_NOTIFICATION_TASK = "missioflow-background-notification";

export type InterventionSyncAction = "upsert" | "delete";
export type InterventionSyncCommand = {
  interventionId: number;
  action: InterventionSyncAction;
};

function asRecord(v: unknown): Record<string, unknown> | null {
  return v && typeof v === "object" ? (v as Record<string, unknown>) : null;
}

/**
 * Extrait { interventionId, action } du payload reçu par la tâche de fond. La
 * forme varie selon la plateforme / la version d'expo-notifications, donc on
 * cherche le bloc `data` à plusieurs emplacements connus, de façon défensive.
 * On keye sur **`action`** (`upsert`/`delete`) + `intervention_id` (le
 * discriminateur de sync fourni par le backend), en ignorant `type`. Retourne
 * null si ce n'est pas un data push de sync intervention exploitable.
 */
export function extractSyncFromData(raw: unknown): InterventionSyncCommand | null {
  const root = asRecord(raw);
  if (!root) {
    return null;
  }

  // Emplacements candidats du dict `data` métier (selon plateforme/shape).
  const notification = asRecord(root.notification);
  const request = asRecord(notification?.request);
  const content = asRecord(request?.content);
  const candidates = [
    asRecord(root.data),
    notification ? asRecord(notification.data) : null,
    content ? asRecord(content.data) : null,
    root, // certains shapes mettent les champs a la racine
  ].filter(Boolean) as Record<string, unknown>[];

  for (const d of candidates) {
    const id = Number(d.intervention_id);
    if (!Number.isFinite(id) || id <= 0) {
      continue;
    }
    // Discriminateur de sync = `action`. Sans action valide -> ce n'est pas un
    // data push de pré-cache (ex: notif visible sans payload sync) -> on ignore.
    const rawAction = String(d.action ?? "");
    if (rawAction !== "upsert" && rawAction !== "delete") {
      continue;
    }
    return { interventionId: id, action: rawAction };
  }

  return null;
}

/**
 * Exécute la sync demandée par un data push. Headless-safe : n'utilise que des
 * services (apiClient lit les tokens en SecureStore, SQLite). Ne throw jamais
 * vers l'OS. upsert -> pull detail+etapes (materialise+cache) ; delete -> purge
 * locale de l'intervention.
 */
export async function runBackgroundInterventionSync(raw: unknown): Promise<boolean> {
  const cmd = extractSyncFromData(raw);
  if (!cmd) {
    return false;
  }
  try {
    await initializeLocalDatabase();
    if (cmd.action === "delete") {
      await deleteInterventionLocally(cmd.interventionId);
    } else {
      // getInterventionById pull le detail ET prefetch les etapes (cache local).
      await getInterventionById(cmd.interventionId);
    }
    return true;
  } catch {
    // best-effort : offline en tache de fond / session expiree -> on abandonne
    // silencieusement, le login/boot ou un prochain push retentera.
    return false;
  }
}

/**
 * Définit la tâche de fond. DOIT être appelée au scope module (au démarrage,
 * avant le rendu React) pour qu'expo-task-manager la connaisse au réveil.
 */
export function defineBackgroundNotificationTask(): void {
  // try/catch IMPERATIF : cette fonction tourne au scope module a CHAQUE
  // demarrage et appelle le natif expo-task-manager. Si ce bundle JS est OTA'd
  // sur un APK qui n'a PAS encore le module natif (rebuild pas fait), l'appel
  // natif jetterait -> crash au lancement. On degrade en silence : pas de tache
  // de fond tant que l'APK n'est pas rebuilde, mais l'app demarre normalement.
  try {
    const TaskManager = loadTaskManager();
    if (!TaskManager) {
      return;
    }
    if (TaskManager.isTaskDefined(BACKGROUND_NOTIFICATION_TASK)) {
      return;
    }
    TaskManager.defineTask(BACKGROUND_NOTIFICATION_TASK, async ({ data, error }) => {
      if (error) {
        return;
      }
      await runBackgroundInterventionSync(data);
    });
  } catch {
    // Module natif absent (OTA sur ancien APK) -> couche 2 inactive, couche 1
    // reste le filet. Aucun crash.
  }
}

/**
 * Enregistre la tâche auprès de l'OS pour les notifications en arrière-plan.
 * Best-effort (ne throw jamais) : à appeler une fois après le setup push.
 */
export async function registerBackgroundNotificationTask(): Promise<void> {
  try {
    const Notifications = loadNotifications();
    if (!Notifications) {
      return;
    }
    await Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK);
  } catch {
    // OS qui refuse / non supporte -> on ignore, la couche 1 reste le filet.
  }
}
