import { Platform } from "react-native";
import * as Notifications from "expo-notifications";
import * as Device from "expo-device";
import * as SecureStore from "expo-secure-store";
import * as Crypto from "expo-crypto";
import Constants from "expo-constants";
import apiClient from "./apiClient";
import type { ApiEnvelope } from "../types/auth";

const DEVICE_ID_KEY = "mf_device_id";
const ANDROID_CHANNEL_ID = "missions";

/**
 * Configure la presentation des notifications recues quand l'app est au
 * premier plan. Sans ce handler, Android/iOS n'affichent rien tant que
 * l'app est ouverte. On affiche banniere + liste pour que le technicien
 * voie tout de suite qu'une mission a ete modifiee.
 */
export function configureNotificationHandler(): void {
  Notifications.setNotificationHandler({
    handleNotification: async () => ({
      shouldShowBanner: true,
      shouldShowList: true,
      shouldPlaySound: true,
      shouldSetBadge: false,
    }),
  });
}

async function ensureAndroidChannel(): Promise<void> {
  if (Platform.OS !== "android") {
    return;
  }
  await Notifications.setNotificationChannelAsync(ANDROID_CHANNEL_ID, {
    name: "Missions",
    importance: Notifications.AndroidImportance.HIGH,
    vibrationPattern: [0, 250, 250, 250],
    lightColor: "#1e56a8",
  });
}

/**
 * Identifiant stable de l'installation, persiste dans SecureStore. Sert de
 * cle (user_id, device_id) cote backend pour qu'un meme telephone ne cree
 * qu'une ligne mobile_devices, et que deux telephones du meme technicien
 * recoivent chacun leurs push.
 */
export async function getOrCreateDeviceId(): Promise<string> {
  const existing = await SecureStore.getItemAsync(DEVICE_ID_KEY);
  if (existing && existing.trim() !== "") {
    return existing;
  }
  const generated = Crypto.randomUUID();
  await SecureStore.setItemAsync(DEVICE_ID_KEY, generated);
  return generated;
}

function resolveProjectId(): string | undefined {
  const fromExtra = (
    Constants.expoConfig?.extra as { eas?: { projectId?: string } } | undefined
  )?.eas?.projectId;
  const fromEas = (Constants as unknown as { easConfig?: { projectId?: string } })
    .easConfig?.projectId;
  const candidate = fromExtra || fromEas;
  return candidate && candidate.trim() !== "" ? candidate : undefined;
}

async function ensurePermissionGranted(): Promise<boolean> {
  const settings = await Notifications.getPermissionsAsync();
  if (settings.granted) {
    return true;
  }
  const request = await Notifications.requestPermissionsAsync();
  return request.granted;
}

/**
 * Demande la permission, recupere le token Expo push de l'appareil.
 * Retourne null si : emulateur (pas de push reel), permission refusee, ou
 * impossibilite de generer le token (projet Expo non configure). Aucun throw
 * : la registration push ne doit jamais bloquer le login.
 */
export async function registerForPushNotificationsAsync(): Promise<string | null> {
  if (!Device.isDevice) {
    return null;
  }

  await ensureAndroidChannel();

  const granted = await ensurePermissionGranted();
  if (!granted) {
    return null;
  }

  try {
    const projectId = resolveProjectId();
    const tokenResponse = await Notifications.getExpoPushTokenAsync(
      projectId ? { projectId } : undefined
    );
    return tokenResponse.data || null;
  } catch {
    return null;
  }
}

/**
 * Enregistre le token Expo aupres du backend (table mobile_devices). Appele
 * apres login et au demarrage si une session est restauree. Best-effort.
 */
export async function registerPushToken(expoPushToken: string): Promise<boolean> {
  const deviceId = await getOrCreateDeviceId();
  try {
    const response = await apiClient.post<ApiEnvelope<unknown>>(
      "/mobile/register_device.php",
      {
        device_id: deviceId,
        device_name: Device.deviceName || Platform.OS,
        platform: Platform.OS,
        expo_push_token: expoPushToken,
      }
    );
    return response.data?.success === true;
  } catch {
    return false;
  }
}

/**
 * Flux complet a appeler quand l'utilisateur est authentifie : recupere le
 * token et l'envoie au backend. Retourne le token (ou null). Ne throw jamais.
 */
export async function syncPushRegistration(): Promise<string | null> {
  const token = await registerForPushNotificationsAsync();
  if (!token) {
    return null;
  }
  await registerPushToken(token);
  return token;
}
