import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { render, waitFor } from "@testing-library/react";
import type { MessagePayload } from "firebase/messaging";
import FcmInit from "@/components/FcmInit";

const push = vi.fn();
const { toast } = vi.hoisted(() => {
  const toastMock = Object.assign(vi.fn(), {
    success: vi.fn(),
    error: vi.fn(),
    warning: vi.fn(),
    info: vi.fn(),
  });
  return { toast: toastMock };
});
const {
  getCurrentFcmToken,
  getMessagingServiceWorkerRegistration,
  onForegroundMessage,
  hasPendingFcmTokenSync,
  isPushEnvironmentAvailable,
} = vi.hoisted(() => ({
  getCurrentFcmToken: vi.fn(),
  getMessagingServiceWorkerRegistration: vi.fn(),
  onForegroundMessage: vi.fn(),
  hasPendingFcmTokenSync: vi.fn(),
  isPushEnvironmentAvailable: vi.fn(() => true),
}));

vi.mock("next/navigation", () => ({
  useRouter: () => ({ push }),
}));

vi.mock("sonner", () => ({
  toast,
}));

vi.mock("@/lib/firebase/messaging", () => ({
  getCurrentFcmToken,
  getMessagingServiceWorkerRegistration,
  hasPendingFcmTokenSync,
  isPushEnvironmentAvailable,
  onForegroundMessage,
}));

const { refreshUnreadNotificationCount } = vi.hoisted(() => ({
  refreshUnreadNotificationCount: vi.fn(),
}));

vi.mock("@/lib/notifications/count", () => ({
  refreshUnreadNotificationCount,
}));

describe("FcmInit", () => {
  const swReg = { scope: "/" } as ServiceWorkerRegistration;
  const unsubscribe = vi.fn();
  let foregroundHandler: ((payload: MessagePayload) => void) | undefined;

  beforeEach(() => {
    vi.clearAllMocks();
    foregroundHandler = undefined;
    getMessagingServiceWorkerRegistration.mockResolvedValue(swReg);
    getCurrentFcmToken.mockResolvedValue("TEST_FCM_TOKEN_123");
    hasPendingFcmTokenSync.mockReturnValue(false);
    onForegroundMessage.mockImplementation((handler) => {
      foregroundHandler = handler;
      return unsubscribe;
    });
  });

  afterEach(() => {
    vi.unstubAllGlobals();
  });

  it("registers foreground messaging once and cleans up on unmount", async () => {
    const { unmount } = render(<FcmInit />);

    await waitFor(() => {
      expect(getMessagingServiceWorkerRegistration).toHaveBeenCalledTimes(1);
      expect(getCurrentFcmToken).toHaveBeenCalledWith({
        requestPermission: true,
        swReg,
      });
      expect(onForegroundMessage).toHaveBeenCalledTimes(1);
    });

    unmount();
    expect(unsubscribe).toHaveBeenCalledTimes(1);
  });

  it("detects pending token sync without inventing a backend call", async () => {
    hasPendingFcmTokenSync.mockReturnValue(true);

    render(<FcmInit />);

    await waitFor(() => {
      expect(hasPendingFcmTokenSync).toHaveBeenCalledWith("TEST_FCM_TOKEN_123");
    });
  });

  it("shows a success toast for settlement_accepted foreground messages", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: { type: "settlement_accepted", title: "تم القبول", body: "تفاصيل" },
    } as unknown as MessagePayload);

    expect(toast.success).toHaveBeenCalledWith(
      "تم القبول",
      expect.objectContaining({
        description: "تفاصيل",
        action: expect.objectContaining({ label: "عرض" }),
      }),
    );
    expect(refreshUnreadNotificationCount).toHaveBeenCalledTimes(1);
    expect(push).not.toHaveBeenCalled();
  });

  it("ignores unknown non-provider notification types", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: { type: "rider_trip_started", title: "رحلة", body: "تفاصيل" },
    } as unknown as MessagePayload);

    expect(toast).not.toHaveBeenCalled();
    expect(toast.success).not.toHaveBeenCalled();
  });

  it("uses notification.title and notification.body when the payload includes a notification block", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      notification: { title: "عنوان النظام", body: "نص النظام" },
      data: { type: "entity_approved" },
    } as unknown as MessagePayload);

    expect(toast.success).toHaveBeenCalledWith(
      "عنوان النظام",
      expect.objectContaining({ description: "نص النظام" }),
    );
  });

  it("navigates when the toast action is triggered", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: { type: "entity_rejected", title: "مرفوض", body: "سبب" },
    } as unknown as MessagePayload);

    const toastOptions = toast.error.mock.calls[0]?.[1] as {
      action?: { onClick?: () => void };
    };
    toastOptions.action?.onClick?.();

    expect(push).toHaveBeenCalledWith("/profile");
  });

  it.each([
    ["settlement_rejected", toast.warning, "/settlements"],
    ["entity_needs_approval", toast.info, "/profile"],
    ["complain_status_changed", toast, "/complaints"],
    ["investor_verification_result", toast, "/profile"],
  ] as const)(
    "routes %s notifications through the expected toast variant and action",
    async (type, toastFn, route) => {
      render(<FcmInit />);

      await waitFor(() => expect(foregroundHandler).toBeDefined());

      foregroundHandler?.({
        data: { type, title: "عنوان", body: "نص" },
      } as unknown as MessagePayload);

      expect(toastFn).toHaveBeenCalledWith(
        "عنوان",
        expect.objectContaining({
          description: "نص",
          action: expect.objectContaining({ label: "عرض" }),
        }),
      );

      const toastOptions = toastFn.mock.calls[0]?.[1] as {
        action?: { onClick?: () => void };
      };
      toastOptions.action?.onClick?.();
      expect(push).toHaveBeenCalledWith(route);
    },
  );

  it("routes profile update approval notifications to the profile page", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: {
        type: "profile_update_approved",
        action_url: "provider/profile",
        title: "تمت الموافقة",
        body: "وافق الأدمن على تعديل بياناتك",
      },
    } as unknown as MessagePayload);

    const toastOptions = toast.success.mock.calls[0]?.[1] as {
      action?: { onClick?: () => void };
    };
    toastOptions.action?.onClick?.();

    expect(push).toHaveBeenCalledWith("/profile");
  });

  it("routes complaint notifications to complaint details from action_url", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: {
        type: "complain_status_changed",
        action_url: "provider/complains/13",
        title: "تحديث الشكوى",
        body: "تم تحديث الحالة",
      },
    } as unknown as MessagePayload);

    const toastOptions = toast.mock.calls[0]?.[1] as {
      action?: { onClick?: () => void };
    };
    toastOptions.action?.onClick?.();

    expect(push).toHaveBeenCalledWith("/complaints?id=13");
  });

  it("refreshes unread notification count after showing a default toast", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: { type: "complain_status_changed", title: "تحديث", body: "" },
    } as unknown as MessagePayload);

    expect(refreshUnreadNotificationCount).toHaveBeenCalledTimes(1);
  });

  it("skips FCM initialization when push environment is unavailable", async () => {
    isPushEnvironmentAvailable.mockReturnValueOnce(false);

    render(<FcmInit />);

    await waitFor(() => {
      expect(isPushEnvironmentAvailable).toHaveBeenCalled();
    });
    expect(getMessagingServiceWorkerRegistration).not.toHaveBeenCalled();
    expect(getCurrentFcmToken).not.toHaveBeenCalled();
    expect(onForegroundMessage).not.toHaveBeenCalled();
  });

  it("skips FCM initialization when service worker registration fails", async () => {
    getMessagingServiceWorkerRegistration.mockResolvedValueOnce(null);

    render(<FcmInit />);

    await waitFor(() => {
      expect(getMessagingServiceWorkerRegistration).toHaveBeenCalledTimes(1);
    });
    expect(getCurrentFcmToken).not.toHaveBeenCalled();
    expect(onForegroundMessage).not.toHaveBeenCalled();
  });

  it("shows a routeless default toast for untyped foreground messages", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: { title: "إشعار عام", body: "تفاصيل عامة" },
    } as unknown as MessagePayload);

    expect(toast).toHaveBeenCalledWith(
      "إشعار عام",
      expect.objectContaining({
        description: "تفاصيل عامة",
        duration: 6000,
      }),
    );
    expect(toast.success).not.toHaveBeenCalled();
  });

  it("uses fallback title and empty description when notification content is absent", async () => {
    render(<FcmInit />);
    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({ data: {} } as unknown as MessagePayload);

    expect(toast).toHaveBeenCalledWith(
      "إشعار جديد",
      expect.objectContaining({ description: undefined, duration: 6000 }),
    );
  });

  it("still refreshes unread count when window is unavailable", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    const savedWindow = globalThis.window;
    // @ts-expect-error simulate SSR
    delete globalThis.window;

    foregroundHandler?.({
      data: { type: "settlement_accepted", title: "تم", body: "تفاصيل" },
    } as unknown as MessagePayload);

    expect(toast.success).toHaveBeenCalled();
    expect(refreshUnreadNotificationCount).toHaveBeenCalledTimes(1);

    globalThis.window = savedWindow;
  });

  it("uses the default toast variant for types without a dedicated variant", async () => {
    render(<FcmInit />);

    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: { type: "investor_verification_result", title: "تحقق", body: "" },
    } as unknown as MessagePayload);

    expect(toast).toHaveBeenCalledWith(
      "تحقق",
      expect.objectContaining({ description: undefined }),
    );
    expect(toast.success).not.toHaveBeenCalled();
    expect(toast.warning).not.toHaveBeenCalled();
    expect(toast.info).not.toHaveBeenCalled();
  });

  it("safely ignores unknown provider notification types", async () => {
    render(<FcmInit />);
    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: { type: "provider_future_type", title: "تحديث", body: "تفاصيل" },
    } as unknown as MessagePayload);

    expect(toast).toHaveBeenCalledWith(
      "تحديث",
      expect.objectContaining({ description: "تفاصيل", duration: 6000 }),
    );
    expect(push).not.toHaveBeenCalled();
  });

  it("shows an info toast for admin_notify with navigation to notifications", async () => {
    render(<FcmInit />);
    await waitFor(() => expect(foregroundHandler).toBeDefined());

    foregroundHandler?.({
      data: {
        type: "admin_notify",
        title: "تنبيه من الإدارة",
        body: "يرجى مراجعة حسابك",
      },
    } as unknown as MessagePayload);

    expect(toast.info).toHaveBeenCalledWith(
      "تنبيه من الإدارة",
      expect.objectContaining({
        description: "يرجى مراجعة حسابك",
        duration: 8000,
        action: expect.objectContaining({ label: "عرض" }),
      }),
    );

    const toastOptions = toast.info.mock.calls[0]?.[1] as {
      action?: { onClick?: () => void };
    };
    toastOptions.action?.onClick?.();
    expect(push).toHaveBeenCalledWith("/notification");
  });
});
