import * as Notifications from "expo-notifications";
import * as SecureStore from "expo-secure-store";
import apiClient from "../../src/services/apiClient";
import {
  configureNotificationHandler,
  getOrCreateDeviceId,
  registerForPushNotificationsAsync,
  registerPushToken,
  syncPushRegistration,
} from "../../src/services/pushNotifications";

// Mutable pour simuler emulateur (false) vs device physique (true). Le prefixe
// "mock" est requis par jest pour referencer la variable dans la factory.
let mockIsDevice = true;
jest.mock("expo-device", () => ({
  get isDevice() {
    return mockIsDevice;
  },
  deviceName: "Test Device",
}));

jest.mock("../../src/services/apiClient", () => ({
  __esModule: true,
  default: { post: jest.fn() },
}));

const mockedNotifications = Notifications as jest.Mocked<typeof Notifications>;
const mockedSecureStore = SecureStore as jest.Mocked<typeof SecureStore>;
const mockedPost = apiClient.post as jest.Mock;

beforeEach(() => {
  jest.clearAllMocks();
  mockIsDevice = true;
  mockedNotifications.getPermissionsAsync.mockResolvedValue({ granted: true } as never);
  mockedNotifications.requestPermissionsAsync.mockResolvedValue({ granted: true } as never);
  mockedNotifications.getExpoPushTokenAsync.mockResolvedValue({
    data: "ExponentPushToken[abc]",
  } as never);
  mockedPost.mockResolvedValue({ data: { success: true } });
});

describe("configureNotificationHandler", () => {
  it("enregistre un handler de presentation", () => {
    configureNotificationHandler();
    expect(mockedNotifications.setNotificationHandler).toHaveBeenCalledTimes(1);
  });
});

describe("getOrCreateDeviceId", () => {
  it("retourne l'id existant sans en generer un nouveau", async () => {
    mockedSecureStore.getItemAsync.mockResolvedValueOnce("device-123");
    const id = await getOrCreateDeviceId();
    expect(id).toBe("device-123");
    expect(mockedSecureStore.setItemAsync).not.toHaveBeenCalled();
  });

  it("genere et persiste un id quand aucun n'existe", async () => {
    mockedSecureStore.getItemAsync.mockResolvedValueOnce(null);
    const id = await getOrCreateDeviceId();
    expect(id).toBe("expo-crypto-uuid");
    expect(mockedSecureStore.setItemAsync).toHaveBeenCalledWith("mf_device_id", "expo-crypto-uuid");
  });
});

describe("registerForPushNotificationsAsync", () => {
  it("retourne null sur emulateur (pas de device physique)", async () => {
    mockIsDevice = false;
    const token = await registerForPushNotificationsAsync();
    expect(token).toBeNull();
    expect(mockedNotifications.getExpoPushTokenAsync).not.toHaveBeenCalled();
  });

  it("demande la permission si elle n'est pas accordee", async () => {
    mockedNotifications.getPermissionsAsync.mockResolvedValueOnce({ granted: false } as never);
    const token = await registerForPushNotificationsAsync();
    expect(mockedNotifications.requestPermissionsAsync).toHaveBeenCalled();
    expect(token).toBe("ExponentPushToken[abc]");
  });

  it("retourne null si la permission est refusee", async () => {
    mockedNotifications.getPermissionsAsync.mockResolvedValueOnce({ granted: false } as never);
    mockedNotifications.requestPermissionsAsync.mockResolvedValueOnce({ granted: false } as never);
    const token = await registerForPushNotificationsAsync();
    expect(token).toBeNull();
  });

  it("retourne le token quand tout est ok", async () => {
    const token = await registerForPushNotificationsAsync();
    expect(token).toBe("ExponentPushToken[abc]");
  });

  it("retourne null si la generation du token echoue", async () => {
    mockedNotifications.getExpoPushTokenAsync.mockRejectedValueOnce(new Error("no project"));
    const token = await registerForPushNotificationsAsync();
    expect(token).toBeNull();
  });
});

describe("registerPushToken", () => {
  it("POST le token au backend et renvoie true", async () => {
    mockedSecureStore.getItemAsync.mockResolvedValueOnce("device-123");
    const ok = await registerPushToken("ExponentPushToken[abc]");
    expect(ok).toBe(true);
    expect(mockedPost).toHaveBeenCalledWith(
      "/mobile/register_device.php",
      expect.objectContaining({
        device_id: "device-123",
        expo_push_token: "ExponentPushToken[abc]",
        platform: expect.any(String),
      })
    );
  });

  it("renvoie false si le backend echoue", async () => {
    mockedPost.mockRejectedValueOnce(new Error("network"));
    const ok = await registerPushToken("ExponentPushToken[abc]");
    expect(ok).toBe(false);
  });

  it("renvoie false si le backend repond success=false", async () => {
    mockedPost.mockResolvedValueOnce({ data: { success: false } });
    const ok = await registerPushToken("ExponentPushToken[abc]");
    expect(ok).toBe(false);
  });
});

describe("syncPushRegistration", () => {
  it("ne POST rien si aucun token n'est obtenu", async () => {
    mockIsDevice = false;
    const token = await syncPushRegistration();
    expect(token).toBeNull();
    expect(mockedPost).not.toHaveBeenCalled();
  });

  it("enregistre le token quand il est obtenu", async () => {
    const token = await syncPushRegistration();
    expect(token).toBe("ExponentPushToken[abc]");
    expect(mockedPost).toHaveBeenCalled();
  });
});
