import { beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import NotificationItem, {
  type NotificationData,
} from "@/components/notifications/NotificationItem";
import { toast } from "sonner";

const push = vi.fn();

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

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

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

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

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

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

function buildNotification(
  overrides: Partial<NotificationData> = {},
): NotificationData {
  return {
    id: "notif-1",
    type: "settlement_accepted",
    title: "تم قبول التسوية",
    body: "تم قبول التسوية",
    is_read: false,
    created_at: "2026-01-01",
    ...overrides,
  };
}

describe("NotificationItem", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("renders separate primary and delete native buttons", () => {
    render(
      <NotificationItem
        notification={buildNotification()}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    const buttons = screen.getAllByRole("button");
    expect(buttons).toHaveLength(2);
    expect(buttons[0]).toHaveAttribute("type", "button");
    expect(buttons[1]).toHaveAttribute("type", "button");
    expect(screen.getByRole("button", { name: "حذف الإشعار" })).toBeInTheDocument();
  });

  it("marks unread notifications as read and navigates on primary activation", async () => {
    const onReadOptimistic = vi.fn();
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.successCase?.({ key: "success", data: null, msg: "" });
      return { key: "success", data: null, msg: "" };
    });

    render(
      <NotificationItem
        notification={buildNotification({ is_read: false })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={onReadOptimistic}
      />,
    );

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

    expect(onReadOptimistic).toHaveBeenCalledWith("notif-1");
    expect(apiFetch).toHaveBeenCalledWith(
      "/provider/notifications/notif-1/read",
      expect.objectContaining({ method: "PATCH" }),
    );
    expect(refreshUnreadNotificationCount).toHaveBeenCalledTimes(1);
    expect(push).toHaveBeenCalledWith("/settlements");
  });

  it("does not mark already-read notifications as read but still navigates", async () => {
    const onReadOptimistic = vi.fn();
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({ is_read: true })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={onReadOptimistic}
      />,
    );

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

    expect(onReadOptimistic).not.toHaveBeenCalled();
    expect(apiFetch).not.toHaveBeenCalled();
    expect(push).toHaveBeenCalledWith("/settlements");
  });

  it("invokes only delete behavior when the delete button is activated", async () => {
    const onDeleteOptimistic = vi.fn();
    const onReadOptimistic = vi.fn();
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({ is_read: false })}
        onDeleteOptimistic={onDeleteOptimistic}
        onReadOptimistic={onReadOptimistic}
      />,
    );

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

    expect(onDeleteOptimistic).toHaveBeenCalledWith("notif-1");
    expect(apiFetch).toHaveBeenCalledWith(
      "/provider/delete-notification/notif-1",
      expect.objectContaining({ method: "DELETE" }),
    );
    expect(onReadOptimistic).not.toHaveBeenCalled();
    expect(push).not.toHaveBeenCalled();
  });

  it.each([
    ["settlement_accepted", "تم القبول", "ri-checkbox-circle-fill"],
    ["settlement_transferred", "تم التحويل", "ri-checkbox-circle-fill"],
    ["settlement_rejected", "تم الرفض", "ri-close-circle-fill"],
  ] as const)(
    "renders %s notifications with the expected icon and route",
    async (type, title, iconClass) => {
      const user = userEvent.setup();

      render(
        <NotificationItem
          notification={buildNotification({ type, title, body: title })}
          onDeleteOptimistic={vi.fn()}
          onReadOptimistic={vi.fn()}
        />,
      );

      expect(document.querySelector(`.${iconClass}`)).toBeInTheDocument();
      await user.click(screen.getByRole("button", { name: new RegExp(title) }));
      expect(push).toHaveBeenCalledWith("/settlements");
    },
  );

  it.each([
    ["approved", "ri-shield-check-fill"],
    ["active", "ri-shield-check-fill"],
    ["verified", "ri-shield-check-fill"],
  ] as const)(
    "renders approved verification notifications for status %s",
    (status, iconClass) => {
      render(
        <NotificationItem
          notification={buildNotification({
            type: "investor_verification_result",
            title: "نتيجة التحقق",
            body: "تم التحقق",
            template_data: { status },
          })}
          onDeleteOptimistic={vi.fn()}
          onReadOptimistic={vi.fn()}
        />,
      );

      expect(document.querySelector(`.${iconClass}`)).toBeInTheDocument();
    },
  );

  it("renders rejected verification notifications with the warning icon", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "investor_verification_result",
          title: "رفض التحقق",
          body: "لم يتم التحقق",
          template_data: { status: "rejected" },
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    expect(document.querySelector(".ri-error-warning-fill")).toBeInTheDocument();
    await user.click(screen.getByRole("button", { name: /رفض التحقق/i }));
    expect(push).toHaveBeenCalledWith("/profile");
  });

  it("navigates to notifications for admin notifications", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "admin_notify",
          title: "تنبيه إداري",
          body: "رسالة من الإدارة",
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    expect(document.querySelector(".ri-information-fill")).toBeInTheDocument();
    await user.click(screen.getByRole("button", { name: /تنبيه إداري/i }));
    expect(push).toHaveBeenCalledWith("/notification");
  });

  it("navigates to complaint details for the real backend payload", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "complain_status_changed",
          title: "تحديث حالة الشكوى",
          body: "تم تحديث حالة شكواك رقم CP-20260715-Q4MGLDVP إلى Completed.",
          action_url: "provider/complains/13",
          data: {
            type: "complain_status_changed",
            model_id: "13",
            params: {
              complain_number: "CP-20260715-Q4MGLDVP",
              ticket_id: 13,
              status: "Completed",
            },
          },
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    await user.click(screen.getByRole("button", { name: /تحديث حالة الشكوى/i }));
    expect(push).toHaveBeenCalledWith("/complaints?id=13");
  });

  it("navigates to complaint details when complain_id is present", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "complain_status_changed",
          title: "تحديث الشكوى",
          body: "تم تحديث حالة الشكوى",
          template_data: { complain_id: 42 },
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    await user.click(screen.getByRole("button", { name: /تحديث الشكوى/i }));
    expect(push).toHaveBeenCalledWith("/complaints?id=42");
  });

  it("navigates to complaint details when template_data is a JSON string", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "complain_status_changed",
          title: "تحديث الشكوى",
          body: "تم تحديث حالة الشكوى",
          template_data: '{"complain_id":55}' as unknown as NotificationData["template_data"],
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    await user.click(screen.getByRole("button", { name: /تحديث الشكوى/i }));
    expect(push).toHaveBeenCalledWith("/complaints?id=55");
  });

  it("navigates to complaints list when complaint id is missing", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "complain_status_changed",
          title: "تحديث الشكوى",
          body: "تم تحديث حالة الشكوى",
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    await user.click(screen.getByRole("button", { name: /تحديث الشكوى/i }));
    expect(push).toHaveBeenCalledWith("/complaints");
  });

  it("navigates to profile for entity_approved notifications", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "entity_approved",
          title: "تمت الموافقة على تعديل البيانات",
          body: "وافق الأدمن على طلب تعديل بياناتك.",
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    await user.click(
      screen.getByRole("button", { name: /تمت الموافقة على تعديل البيانات/i }),
    );
    expect(push).toHaveBeenCalledWith("/profile");
  });

  it("navigates to profile for profile update approval notifications", async () => {
    const user = userEvent.setup();

    render(
      <NotificationItem
        notification={buildNotification({
          type: "profile_update_approved",
          title: "تمت الموافقة على تعديل البيانات",
          body: "تم قبول طلب تعديل الملف الشخصي.",
          action_url: "provider/profile",
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    await user.click(
      screen.getByRole("button", { name: /تمت الموافقة على تعديل البيانات/i }),
    );
    expect(push).toHaveBeenCalledWith("/profile");
  });

  it("renders the default presentation for unknown notification types", () => {
    render(
      <NotificationItem
        notification={buildNotification({
          type: "custom_event",
          title: "إشعار عام",
          body: "محتوى عام",
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    expect(document.querySelector(".ri-notification-3-fill")).toBeInTheDocument();
    expect(screen.getByText("محتوى عام")).toBeInTheDocument();
  });

  it("substitutes template placeholders in the notification body", () => {
    render(
      <NotificationItem
        notification={buildNotification({
          body: "تم تحويل :amount ريال للتسوية :settlement_id",
          template_data: { amount: "500", settlement_id: "88" },
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    expect(
      screen.getByText("تم تحويل 500 ريال للتسوية 88"),
    ).toBeInTheDocument();
  });

  it("prefers the Arabic body when template data is absent", () => {
    render(
      <NotificationItem
        notification={buildNotification({
          body: "English body",
          body_ar: "نص عربي",
        })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    expect(screen.getByText("نص عربي")).toBeInTheDocument();
    expect(screen.queryByText("English body")).not.toBeInTheDocument();
  });

  it("shows unread styling and hides read styling for unread notifications", () => {
    const { rerender } = render(
      <NotificationItem
        notification={buildNotification({ is_read: false, title: "غير مقروء" })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    expect(screen.getByText("غير مقروء").className).toContain("text-white");
    expect(document.querySelector(".bg-\\[\\#FCD704\\]")).toBeInTheDocument();

    rerender(
      <NotificationItem
        notification={buildNotification({ is_read: true, title: "مقروء" })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    expect(screen.getByText("مقروء").className).toContain("text-gray-300");
  });

  it("shows a toast when marking a notification as read fails", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.errorCase?.({ key: "fail", data: null, msg: "فشل القراءة" });
      return { key: "fail", data: null, msg: "فشل القراءة" };
    });

    render(
      <NotificationItem
        notification={buildNotification({ is_read: false })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

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

    expect(toast.error).toHaveBeenCalledWith("فشل القراءة");
  });

  it("uses the default read-failure toast when the API error has no msg", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.errorCase?.({ key: "fail", data: null, msg: "" });
      return { key: "fail", data: null, msg: "" };
    });

    render(
      <NotificationItem
        notification={buildNotification({ is_read: false })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

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

    expect(toast.error).toHaveBeenCalledWith("تعذّر تعليم الإشعار كمقروء");
  });

  it("refreshes unread count after a successful read", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.successCase?.({ key: "success", data: null, msg: "" });
      return { key: "success", data: null, msg: "" };
    });

    render(
      <NotificationItem
        notification={buildNotification({ is_read: false })}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

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

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

  it("refreshes unread count after a successful delete", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.successCase?.({ key: "success", data: null, msg: "" });
      return { key: "success", data: null, msg: "" };
    });

    render(
      <NotificationItem
        notification={buildNotification()}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

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

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

  it("shows a toast when delete fails", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.errorCase?.({ key: "fail", data: null, msg: "فشل الحذف" });
      return { key: "fail", data: null, msg: "فشل الحذف" };
    });

    render(
      <NotificationItem
        notification={buildNotification()}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

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

    expect(toast.error).toHaveBeenCalledWith("فشل الحذف");
  });

  it("ignores a second delete click while deletion is already in progress", async () => {
    const user = userEvent.setup();
    let resolveDelete!: () => void;
    const deletePromise = new Promise<void>((resolve) => {
      resolveDelete = resolve;
    });

    vi.mocked(apiFetch).mockImplementation(async (url) => {
      if (url.includes("delete-notification")) {
        await deletePromise;
        return { key: "success", data: null, msg: "" };
      }
      return { key: "success", data: null, msg: "" };
    });

    render(
      <NotificationItem
        notification={buildNotification()}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

    const deleteButton = screen.getByRole("button", { name: "حذف الإشعار" });
    await user.click(deleteButton);
    await user.click(deleteButton);

    expect(apiFetch).toHaveBeenCalledTimes(1);
    resolveDelete();
  });

  it("uses the default delete-failure toast when the API error has no msg", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.errorCase?.({ key: "fail", data: null, msg: "" });
      return { key: "fail", data: null, msg: "" };
    });

    render(
      <NotificationItem
        notification={buildNotification()}
        onDeleteOptimistic={vi.fn()}
        onReadOptimistic={vi.fn()}
      />,
    );

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

    expect(toast.error).toHaveBeenCalledWith("تعذّر حذف الإشعار");
  });
});
