// Mocks communs pour l'environnement Expo / RN
// Les mocks specifiques a un test vivent dans le fichier de test lui-meme.

jest.mock("expo-crypto", () => ({
  randomUUID: jest.fn().mockReturnValue("expo-crypto-uuid"),
  getRandomBytesAsync: jest
    .fn()
    .mockResolvedValue(
      new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
    ),
}));

// Module natif Argon2 (react-native-argon2) : mock deterministe -> meme
// (password, salt) => meme encodedHash, pour que verifyOfflineCredential soit
// testable. En prod c'est le vrai Argon2id (rebuild APK requis, pas d'OTA).
jest.mock("react-native-argon2", () => ({
  __esModule: true,
  default: jest.fn(async (password: string, salt: string) => ({
    rawHash: `raw:${salt}:${password}`,
    encodedHash: `$argon2id$mock$${salt}$${password}`,
  })),
}));

jest.mock("expo-secure-store", () => ({
  getItemAsync: jest.fn(),
  setItemAsync: jest.fn(),
  deleteItemAsync: jest.fn(),
}));

jest.mock("expo-sqlite", () => {
  const execAsync = jest.fn();
  const runAsync = jest.fn();
  const getAllAsync = jest.fn();
  const getFirstAsync = jest.fn();
  const closeAsync = jest.fn();
  const db = { execAsync, runAsync, getAllAsync, getFirstAsync, closeAsync };
  return {
    openDatabaseAsync: jest.fn().mockResolvedValue(db),
    __db: db,
  };
});

jest.mock("expo-network", () => ({
  getNetworkStateAsync: jest.fn().mockResolvedValue({ isConnected: true, isInternetReachable: true }),
  addNetworkStateListener: jest.fn().mockReturnValue({ remove: jest.fn() }),
}));

jest.mock("expo-constants", () => ({
  expoConfig: { extra: {} },
}));

jest.mock("expo-device", () => ({
  isDevice: true,
  deviceName: "Test Device",
}));

jest.mock("expo-notifications", () => ({
  setNotificationHandler: jest.fn(),
  setNotificationChannelAsync: jest.fn().mockResolvedValue(undefined),
  getPermissionsAsync: jest.fn().mockResolvedValue({ granted: true }),
  requestPermissionsAsync: jest.fn().mockResolvedValue({ granted: true }),
  getExpoPushTokenAsync: jest.fn().mockResolvedValue({ data: "ExponentPushToken[xxx]" }),
  addNotificationReceivedListener: jest.fn().mockReturnValue({ remove: jest.fn() }),
  addNotificationResponseReceivedListener: jest.fn().mockReturnValue({ remove: jest.fn() }),
  registerTaskAsync: jest.fn().mockResolvedValue(undefined),
  AndroidImportance: { HIGH: 4 },
  IosAuthorizationStatus: { PROVISIONAL: 3 },
}));

jest.mock("expo-task-manager", () => ({
  defineTask: jest.fn(),
  isTaskDefined: jest.fn().mockReturnValue(false),
}));

jest.mock("expo-camera", () => ({
  CameraView: "CameraView",
  useCameraPermissions: () => [{ granted: true }, jest.fn()],
}));

jest.mock("expo-status-bar", () => ({
  StatusBar: "StatusBar",
}));

jest.mock("react-native-webview", () => ({
  WebView: "WebView",
}));

jest.mock("react-native-signature-canvas", () => "SignatureCanvas");

// Mail composer indisponible par defaut : les tests qui en ont besoin le
// surchargent localement.
jest.mock("expo-mail-composer", () => ({
  isAvailableAsync: jest.fn().mockResolvedValue(false),
  composeAsync: jest.fn().mockResolvedValue({ status: "cancelled" }),
}));

// expo-file-system : mock fonctionnel en memoire (suffisant pour crashLog et
// pour App). Les tests qui veulent inspecter les I/O (ex. fileStorage) le
// surchargent localement, ce mock global est alors ignore pour ce fichier.
jest.mock("expo-file-system", () => {
  const store = new Map<string, string>();
  const dirs = new Set<string>();
  const join = (parent: string | { uri: string }, name?: string): string => {
    const base = typeof parent === "string" ? parent : parent.uri;
    return name ? `${base.replace(/\/$/, "")}/${name}` : base;
  };
  class Directory {
    uri: string;
    constructor(parent: string | Directory, name?: string) {
      this.uri = `${join(parent, name)}/`;
    }
    get exists(): boolean {
      return dirs.has(this.uri);
    }
    create(): void {
      dirs.add(this.uri);
    }
    delete(): void {
      dirs.delete(this.uri);
    }
  }
  class File {
    uri: string;
    constructor(parent: string | Directory, name?: string) {
      this.uri = join(parent, name);
    }
    get exists(): boolean {
      return store.has(this.uri);
    }
    create(): void {
      if (!store.has(this.uri)) {
        store.set(this.uri, "");
      }
    }
    write(content: string): void {
      store.set(this.uri, content);
    }
    textSync(): string {
      return store.get(this.uri) ?? "";
    }
    text(): Promise<string> {
      return Promise.resolve(store.get(this.uri) ?? "");
    }
    delete(): void {
      store.delete(this.uri);
    }
  }
  return { Directory, File, Paths: { document: "file:///doc" }, __store: store, __dirs: dirs };
});

jest.mock("react-native-safe-area-context", () => ({
  SafeAreaProvider: ({ children }: { children: unknown }) => children,
  SafeAreaView: ({ children }: { children: unknown }) => children,
  useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
}));
