import { beforeEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import NotificationsPage from "@/app/notification/page";
import type { NotificationsResponse } from "@/components/notifications/NotificationItem";
import type { ApiEnvelope } from "@/lib/api/types";
import { toast } from "sonner";

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

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

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

vi.mock("@/components/DashboardLayout", () => ({
  default: ({
    children,
    title,
  }: {
    children: React.ReactNode;
    title: string;
  }) => (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  ),
}));

vi.mock("@/lib/api", () => ({
  apiFetch: vi.fn(),
  formatApiMsg: vi.fn((msg: string) => msg),
}));

vi.mock("sonner", () => ({
  toast: { success: vi.fn(), error: vi.fn() },
}));

import { apiFetch, formatApiMsg } from "@/lib/api";

function buildNotificationsResponse(
  overrides: {
    data?: NotificationsResponse["notifications"]["data"];
    pagination?: Partial<NotificationsResponse["notifications"]["pagination"]>;
  } = {},
): NotificationsResponse {
  return {
    notifications: {
      data: overrides.data ?? [
        {
          id: "notif-1",
          type: "settlement_accepted",
          title: "تم قبول التسوية",
          body: "تم قبول طلب التسوية بنجاح",
          is_read: false,
          created_at: "2026-01-01",
        },
        {
          id: "notif-2",
          type: "admin_notify",
          title: "تنبيه إداري",
          body: "رسالة من الإدارة",
          is_read: true,
          created_at: "2026-01-02",
        },
      ],
      pagination: {
        total_items: overrides.data?.length ?? 2,
        count_items: overrides.data?.length ?? 2,
        per_page: 15,
        total_pages: 1,
        current_page: 1,
        next_page_url: null,
        perv_page_url: null,
        ...overrides.pagination,
      },
    },
  };
}

function mockNotificationsFetch({
  response = buildNotificationsResponse(),
  error,
  onUrl,
}: {
  response?: NotificationsResponse;
  error?: string;
  onUrl?: (url: string) => void;
} = {}) {
  vi.mocked(apiFetch).mockImplementation(async (url, options) => {
    if (typeof url === "string") {
      onUrl?.(url);
    }

    if (typeof url === "string" && url.includes("/provider/notifications?page=")) {
      if (error) {
        const envelope = {
          key: "fail",
          msg: error,
        } satisfies ApiEnvelope;
        options?.errorCase?.(envelope);
        return envelope;
      }

      const envelope = {
        key: "success",
        data: response,
        msg: "",
      } satisfies ApiEnvelope<NotificationsResponse>;
      options?.successCase?.(envelope);
      return envelope;
    }

    if (typeof url === "string" && url.includes("/read")) {
      const envelope = {
        key: "success",
        data: null,
        msg: "",
      } satisfies ApiEnvelope;
      options?.successCase?.(envelope);
      return envelope;
    }

    return { key: "success", msg: "" };
  });
}

describe("NotificationsPage", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(formatApiMsg).mockImplementation((msg) => String(msg ?? ""));
  });

  it("renders the page title and loading skeletons on initial fetch", () => {
    vi.mocked(apiFetch).mockImplementation(() => new Promise(() => {}));

    const { container } = render(<NotificationsPage />);

    expect(
      screen.getByRole("heading", { name: "الإشعارات" }),
    ).toBeInTheDocument();
    expect(container.querySelectorAll(".animate-pulse").length).toBeGreaterThan(0);
    expect(screen.getByText("أحدث الإشعارات")).toBeInTheDocument();
  });

  it("renders notification items and the total count on success", async () => {
    mockNotificationsFetch();

    render(<NotificationsPage />);

    expect(await screen.findByText("تم قبول التسوية")).toBeInTheDocument();
    expect(screen.getByText("تنبيه إداري")).toBeInTheDocument();
    expect(screen.getByText("2")).toBeInTheDocument();
    expect(screen.getByText("تم قبول طلب التسوية بنجاح")).toBeInTheDocument();
  });

  it("shows the error state when the notifications request fails", async () => {
    mockNotificationsFetch({ error: "تعذّر تحميل الإشعارات" });

    render(<NotificationsPage />);

    expect(
      await screen.findByText("تعذّر تحميل الإشعارات"),
    ).toBeInTheDocument();
  });

  it("uses the default load error when the API message cannot be formatted", async () => {
    vi.mocked(formatApiMsg).mockReturnValue("");
    mockNotificationsFetch({ error: "رسالة خلفية" });

    render(<NotificationsPage />);

    expect(
      await screen.findByText("تعذّر تحميل الإشعارات، حاول مرة أخرى"),
    ).toBeInTheDocument();
  });

  it("does not crash when the disabled mark-all action is clicked while loading", () => {
    vi.mocked(apiFetch).mockImplementation(() => new Promise(() => {}));

    render(<NotificationsPage />);

    const markAll = screen.getByRole("button", { name: /تعليم الكل كمقروء/i });
    expect(markAll).toBeDisabled();
    expect(() => fireEvent.click(markAll)).not.toThrow();
  });

  it("shows the empty state when there are no notifications", async () => {
    mockNotificationsFetch({
      response: buildNotificationsResponse({ data: [] }),
    });

    render(<NotificationsPage />);

    expect(
      await screen.findByText("لا توجد إشعارات حالياً"),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: /تعليم الكل كمقروء/i }),
    ).toBeDisabled();
  });

  it("marks a single unread notification as read when activated", async () => {
    const user = userEvent.setup();
    mockNotificationsFetch();

    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");

    await user.click(screen.getByRole("button", { name: /تم قبول التسوية/i }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/notifications/notif-1/read",
        expect.objectContaining({ method: "PATCH" }),
      );
    });
    expect(refreshUnreadNotificationCount).toHaveBeenCalled();
  });

  it("marks all notifications as read from the header action", async () => {
    const user = userEvent.setup();
    mockNotificationsFetch();

    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");

    await user.click(
      screen.getByRole("button", { name: /تعليم الكل كمقروء/i }),
    );

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/notifications/read-all",
        expect.objectContaining({ method: "PATCH" }),
      );
    });
    expect(toast.success).toHaveBeenCalledWith("تم تعليم الكل كمقروء");
    expect(refreshUnreadNotificationCount).toHaveBeenCalledTimes(1);
  });

  it("still refreshes unread count when window is unavailable", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (
        typeof url === "string" &&
        url.includes("/provider/notifications?page=")
      ) {
        const envelope = {
          key: "success",
          data: buildNotificationsResponse(),
          msg: "",
        } satisfies ApiEnvelope<NotificationsResponse>;
        options?.successCase?.(envelope);
        return envelope;
      }

      if (typeof url === "string" && url.includes("/read-all")) {
        const envelope = {
          key: "success",
          data: null,
          msg: "",
        } satisfies ApiEnvelope;
        const originalWindow = globalThis.window;
        Object.defineProperty(globalThis, "window", {
          configurable: true,
          writable: true,
          value: undefined,
        });
        try {
          options?.successCase?.(envelope);
        } finally {
          Object.defineProperty(globalThis, "window", {
            configurable: true,
            writable: true,
            value: originalWindow,
          });
        }
        return envelope;
      }

      return { key: "success", msg: "" };
    });

    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");

    await user.click(
      screen.getByRole("button", { name: /تعليم الكل كمقروء/i }),
    );

    await waitFor(() => {
      expect(toast.success).toHaveBeenCalledWith("تم تعليم الكل كمقروء");
    });
    expect(refreshUnreadNotificationCount).toHaveBeenCalledTimes(1);
  });

  it("shows an error toast when marking all notifications as read fails", async () => {
    const user = userEvent.setup();
    mockNotificationsFetch();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (typeof url === "string" && url.includes("/read-all")) {
        const envelope = { key: "fail", msg: "فشل التحديث" } satisfies ApiEnvelope;
        options?.errorCase?.(envelope);
        return envelope;
      }

      if (typeof url === "string" && url.includes("/provider/notifications?page=")) {
        const envelope = {
          key: "success",
          data: buildNotificationsResponse(),
          msg: "",
        } satisfies ApiEnvelope<NotificationsResponse>;
        options?.successCase?.(envelope);
        return envelope;
      }

      return { key: "success", msg: "" };
    });

    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");

    await user.click(
      screen.getByRole("button", { name: /تعليم الكل كمقروء/i }),
    );

    expect(toast.error).toHaveBeenCalledWith("فشل التحديث");
  });

  it("falls back to a default error toast when marking all notifications as read fails without msg", async () => {
    const user = userEvent.setup();
    mockNotificationsFetch();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (typeof url === "string" && url.includes("/read-all")) {
        const envelope = { key: "fail" } satisfies ApiEnvelope;
        options?.errorCase?.(envelope);
        return envelope;
      }

      if (typeof url === "string" && url.includes("/provider/notifications?page=")) {
        const envelope = {
          key: "success",
          data: buildNotificationsResponse(),
          msg: "",
        } satisfies ApiEnvelope<NotificationsResponse>;
        options?.successCase?.(envelope);
        return envelope;
      }

      return { key: "success", msg: "" };
    });

    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");

    await user.click(
      screen.getByRole("button", { name: /تعليم الكل كمقروء/i }),
    );

    expect(toast.error).toHaveBeenCalledWith("حدث خطأ أثناء التحديث");
  });

  it("uses the default mark-all error when formatting returns an empty message", async () => {
    const user = userEvent.setup();
    vi.mocked(formatApiMsg).mockReturnValue("");
    mockNotificationsFetch();
    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (typeof url === "string" && url.includes("/read-all")) {
        const envelope = { key: "fail", msg: "رسالة خلفية" } satisfies ApiEnvelope;
        options?.errorCase?.(envelope);
        return envelope;
      }
      if (typeof url === "string" && url.includes("/provider/notifications?page=")) {
        const envelope = {
          key: "success",
          data: buildNotificationsResponse(),
          msg: "",
        } satisfies ApiEnvelope<NotificationsResponse>;
        options?.successCase?.(envelope);
        return envelope;
      }
      return { key: "success", msg: "" };
    });

    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");
    await user.click(screen.getByRole("button", { name: /تعليم الكل كمقروء/i }));

    expect(toast.error).toHaveBeenCalledWith("حدث خطأ أثناء التحديث");
  });

  it("shows the delete-all API placeholder toast when delete all is clicked", async () => {
    const user = userEvent.setup();
    mockNotificationsFetch();

    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");

    await user.click(screen.getByRole("button", { name: /حذف الكل/i }));

    expect(toast.error).toHaveBeenCalledWith(
      "لم يتم توفير الرابط الصحيح لحذف الكل من قِبل واجهة برمجة التطبيقات (API).",
      { duration: 5000 },
    );
  });

  it("ignores stale notification fetch callbacks after unmount", async () => {
    let resolveFetch!: (envelope: ApiEnvelope<NotificationsResponse>) => void;

    vi.mocked(apiFetch).mockImplementation((_url, options) => {
      return new Promise<ApiEnvelope<NotificationsResponse>>((resolve) => {
        resolveFetch = (envelope) => {
          options?.successCase?.(envelope);
          resolve(envelope);
        };
      });
    });

    const { unmount } = render(<NotificationsPage />);
    await waitFor(() => expect(apiFetch).toHaveBeenCalled());
    unmount();
    resolveFetch({
      key: "success",
      data: buildNotificationsResponse(),
      msg: "",
    });

    expect(screen.queryByText("تم قبول التسوية")).not.toBeInTheDocument();
  });

  it("ignores stale notification errors after unmount", async () => {
    let rejectFetch!: () => void;
    vi.mocked(apiFetch).mockImplementation((_url, options) =>
      new Promise<ApiEnvelope<NotificationsResponse>>((resolve) => {
        rejectFetch = () => {
          const envelope = { key: "fail", msg: "خطأ متأخر" } satisfies ApiEnvelope;
          options?.errorCase?.(envelope);
          resolve(envelope);
        };
      }),
    );

    const { unmount } = render(<NotificationsPage />);
    await waitFor(() => expect(apiFetch).toHaveBeenCalled());
    unmount();
    rejectFetch();

    expect(screen.queryByText("خطأ متأخر")).not.toBeInTheDocument();
  });

  it("removes an item after its delete action invokes the optimistic callback", async () => {
    const user = userEvent.setup();
    mockNotificationsFetch();
    render(<NotificationsPage />);
    await screen.findByText("تم قبول التسوية");

    await user.click(screen.getAllByRole("button", { name: "حذف الإشعار" })[0]);

    expect(screen.queryByText("تم قبول التسوية")).not.toBeInTheDocument();
    expect(apiFetch).toHaveBeenCalledWith(
      "/provider/delete-notification/notif-1",
      expect.objectContaining({ method: "DELETE" }),
    );
  });

  it("refetches notifications when pagination changes", async () => {
    const user = userEvent.setup();
    const fetchedUrls: string[] = [];

    mockNotificationsFetch({
      response: buildNotificationsResponse({
        pagination: {
          total_items: 30,
          count_items: 15,
          per_page: 15,
          total_pages: 2,
          current_page: 1,
          next_page_url: "/provider/notifications?page=2",
          perv_page_url: null,
        },
      }),
      onUrl: (url) => fetchedUrls.push(url),
    });

    render(<NotificationsPage />);
    await screen.findByText("إجمالي 30 إشعار");

    await user.click(screen.getByRole("button", { name: "2" }));

    await waitFor(() => {
      expect(fetchedUrls).toContain("/provider/notifications?page=2");
    });
  });

  it("ignores mark-all-read when the notifications list is empty", async () => {
    mockNotificationsFetch({
      response: buildNotificationsResponse({ data: [] }),
    });
    render(<NotificationsPage />);
    await screen.findByText("لا توجد إشعارات حالياً");

    fireEvent.click(screen.getByRole("button", { name: /تعليم الكل كمقروء/i }));

    expect(toast.success).not.toHaveBeenCalled();
    expect(apiFetch).toHaveBeenCalledTimes(1);
  });

  it("moves through previous and next pagination actions", async () => {
    const user = userEvent.setup();
    const fetchedUrls: string[] = [];
    mockNotificationsFetch({
      response: buildNotificationsResponse({
        pagination: {
          total_items: 45,
          count_items: 15,
          per_page: 15,
          total_pages: 3,
          current_page: 2,
          next_page_url: "/provider/notifications?page=3",
          perv_page_url: "/provider/notifications?page=1",
        },
      }),
      onUrl: (url) => fetchedUrls.push(url),
    });

    render(<NotificationsPage />);
    await screen.findByText("إجمالي 45 إشعار");
    await user.click(screen.getByLabelText("الصفحة التالية"));
    await waitFor(() =>
      expect(fetchedUrls).toContain("/provider/notifications?page=2"),
    );
    await user.click(screen.getByLabelText("الصفحة السابقة"));
    await waitFor(() =>
      expect(fetchedUrls).toContain("/provider/notifications?page=1"),
    );
  });

  it("renders and navigates the five-page pagination window", async () => {
    const user = userEvent.setup();
    const fetchedUrls: string[] = [];
    mockNotificationsFetch({
      response: buildNotificationsResponse({
        pagination: {
          total_items: 90,
          count_items: 15,
          per_page: 15,
          total_pages: 6,
          current_page: 3,
          next_page_url: "/provider/notifications?page=4",
          perv_page_url: "/provider/notifications?page=2",
        },
      }),
      onUrl: (url) => fetchedUrls.push(url),
    });

    render(<NotificationsPage />);
    await screen.findByText("إجمالي 90 إشعار");
    expect(screen.getAllByRole("button", { name: /^[1-5]$/ })).toHaveLength(5);

    await user.click(screen.getByRole("button", { name: "5" }));
    await waitFor(() => {
      expect(fetchedUrls).toContain("/provider/notifications?page=5");
    });
  });
});
