import { beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import ComplaintsPage from "@/app/complaints/page";
import type { Complaint } from "@/components/complaints/ComplaintCard";
import type { ApiEnvelope } from "@/lib/api/types";

const replace = vi.fn();
const { searchParamsState } = vi.hoisted(() => ({
  searchParamsState: {
    id: null as string | null,
  },
}));

vi.mock("next/navigation", () => ({
  useRouter: () => ({ replace, push: vi.fn() }),
  useSearchParams: () => ({
    get: (key: string) => (key === "id" ? searchParamsState.id : null),
  }),
}));

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),
}));

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

interface ComplaintsListData {
  data: Complaint[];
  pagination?: {
    current_page: number;
    last_page: number;
    per_page: number;
    total: number;
  };
}

interface ComplaintDetails {
  id: number | string;
  title: string;
  description: string;
  complain_number: string;
  created_at: string;
  status: string;
  status_translation: string;
  images: string[];
  reply: string | null;
}

function buildComplaint(overrides: Partial<Complaint> = {}): Complaint {
  return {
    id: 1,
    title: "تأخر في الصيانة",
    complain_number: "CMP-1001",
    created_at: "2026-01-15T10:00:00.000Z",
    status: "new",
    status_translation: "جديدة",
    ...overrides,
  };
}

function buildComplaintsListData(
  overrides: Partial<ComplaintsListData> = {},
): ComplaintsListData {
  return {
    data: [buildComplaint()],
    pagination: {
      current_page: 1,
      last_page: 1,
      per_page: 15,
      total: 1,
    },
    ...overrides,
  };
}

function buildComplaintDetails(
  overrides: Partial<ComplaintDetails> = {},
): ComplaintDetails {
  return {
    id: 1,
    title: "تأخر في الصيانة",
    description: "وصف تفصيلي للشكوى",
    complain_number: "CMP-1001",
    created_at: "2026-01-15T10:00:00.000Z",
    status: "new",
    status_translation: "جديدة",
    images: [],
    reply: null,
    ...overrides,
  };
}

function mockComplaintsResponses({
  list = buildComplaintsListData(),
  listError,
  details = buildComplaintDetails(),
}: {
  list?: ComplaintsListData;
  listError?: string;
  details?: ComplaintDetails;
} = {}) {
  vi.mocked(apiFetch).mockImplementation(async (url, options) => {
    if (typeof url === "string" && url.includes("/provider/complains")) {
      if (/\/provider\/complains\/[^?]+/.test(url)) {
        const envelope = {
          key: "success",
          data: details,
          msg: "",
        } satisfies ApiEnvelope<ComplaintDetails>;
        options?.successCase?.(envelope);
        return envelope;
      }

      if (options?.method === "GET") {
        if (listError) {
          const envelope = {
            key: "fail",
            msg: listError,
          } satisfies ApiEnvelope;
          options?.errorCase?.(envelope);
          return envelope;
        }

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

      if (options?.method === "POST") {
        const envelope = {
          key: "success",
          msg: "",
        } satisfies ApiEnvelope;
        options?.successCase?.(envelope);
        return envelope;
      }
    }

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

function getPaginationContainer(): HTMLElement {
  const label = screen.getByText(/الصفحة \d+ من \d+/);
  const container = label.parentElement;
  if (!container) {
    throw new Error("Pagination container not found");
  }
  return container;
}

function getPrevPageButton(): HTMLElement {
  const container = getPaginationContainer();
  const icon = container.querySelector(".ri-arrow-left-s-line");
  if (!icon?.parentElement) {
    throw new Error("Previous page button not found");
  }
  return icon.parentElement as HTMLElement;
}

function getNextPageButton(): HTMLElement {
  const container = getPaginationContainer();
  const icon = container.querySelector(".ri-arrow-right-s-line");
  if (!icon?.parentElement) {
    throw new Error("Next page button not found");
  }
  return icon.parentElement as HTMLElement;
}

function getTitleInput() {
  return screen.getByPlaceholderText("اكتب عنواناً مختصراً");
}

function getDescriptionInput() {
  return screen.getByPlaceholderText("اشرح المشكلة بالتفصيل...");
}

async function fillValidComplaintForm(
  user: ReturnType<typeof userEvent.setup>,
) {
  await user.type(getTitleInput(), "عنوان الشكوى");
  await user.type(
    getDescriptionInput(),
    "وصف مفصل للشكوى يتجاوز عشرة أحرف على الأقل",
  );
}

describe("ComplaintsPage", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    searchParamsState.id = null;
  });

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

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

    expect(screen.getByRole("heading", { name: "الشكاوى" })).toBeInTheDocument();
    expect(container.querySelectorAll(".animate-pulse").length).toBe(6);
  });

  it("shows the error state when the complaints request fails", async () => {
    mockComplaintsResponses({ listError: "تعذّر تحميل الشكاوى" });

    render(<ComplaintsPage />);

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

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

    render(<ComplaintsPage />);

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

  it("shows the empty state when there are no complaints", async () => {
    mockComplaintsResponses({
      list: buildComplaintsListData({ data: [] }),
    });

    render(<ComplaintsPage />);

    expect(await screen.findByText("لا توجد شكاوى")).toBeInTheDocument();
  });

  it("renders complaint cards when the list request succeeds", async () => {
    mockComplaintsResponses({
      list: buildComplaintsListData({
        data: [
          buildComplaint({ id: 1, title: "تأخر في الصيانة" }),
          buildComplaint({
            id: 2,
            title: "مشكلة في الفواتير",
            complain_number: "CMP-1002",
          }),
        ],
      }),
    });

    render(<ComplaintsPage />);

    expect(
      await screen.findByRole("button", { name: /تأخر في الصيانة/i }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: /مشكلة في الفواتير/i }),
    ).toBeInTheDocument();
    expect(screen.getByText("رقم الشكوى: CMP-1001")).toBeInTheDocument();
    expect(screen.getByText("رقم الشكوى: CMP-1002")).toBeInTheDocument();
  });

  it("renders pagination controls when totalPages is greater than one", async () => {
    const user = userEvent.setup();
    mockComplaintsResponses({
      list: buildComplaintsListData({
        pagination: {
          current_page: 1,
          last_page: 3,
          per_page: 15,
          total: 45,
        },
      }),
    });

    render(<ComplaintsPage />);
    await screen.findByText("الصفحة 1 من 3");

    await user.click(getNextPageButton());

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/complains?page=2&perPage=15",
        expect.objectContaining({ method: "GET" }),
      );
    });
    expect(await screen.findByText("الصفحة 2 من 3")).toBeInTheDocument();
  });

  it("navigates to the previous page when the back control is clicked", async () => {
    const user = userEvent.setup();
    mockComplaintsResponses({
      list: buildComplaintsListData({
        pagination: {
          current_page: 1,
          last_page: 3,
          per_page: 15,
          total: 45,
        },
      }),
    });

    render(<ComplaintsPage />);
    await screen.findByText("الصفحة 1 من 3");

    await user.click(getNextPageButton());
    await screen.findByText("الصفحة 2 من 3");

    await user.click(getPrevPageButton());

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/complains?page=1&perPage=15",
        expect.objectContaining({ method: "GET" }),
      );
    });
    expect(await screen.findByText("الصفحة 1 من 3")).toBeInTheDocument();
  });

  it("opens the create modal when the add complaint button is clicked", async () => {
    const user = userEvent.setup();
    mockComplaintsResponses();

    render(<ComplaintsPage />);
    await screen.findByRole("button", { name: /تأخر في الصيانة/i });

    await user.click(screen.getByRole("button", { name: "إضافة شكوى" }));

    expect(screen.getByText("إضافة شكوى جديدة")).toBeInTheDocument();
    expect(getTitleInput()).toBeInTheDocument();
  });

  it("opens the view modal when a complaint card is clicked", async () => {
    const user = userEvent.setup();
    mockComplaintsResponses();

    render(<ComplaintsPage />);
    await user.click(
      await screen.findByRole("button", { name: /تأخر في الصيانة/i }),
    );

    expect(screen.getByText("تفاصيل الشكوى")).toBeInTheDocument();
    expect(await screen.findByText("وصف تفصيلي للشكوى")).toBeInTheDocument();

    expect(apiFetch).toHaveBeenCalledWith("/provider/complains/1", {
      method: "GET",
      successCase: expect.any(Function),
      errorCase: expect.any(Function),
    });
    expect(replace).toHaveBeenCalledWith("/complaints?id=1");
  });

  it("opens complaint details from the id query param", async () => {
    searchParamsState.id = "1";
    mockComplaintsResponses();

    render(<ComplaintsPage />);

    expect(await screen.findByText("تفاصيل الشكوى")).toBeInTheDocument();
    expect(await screen.findByText("وصف تفصيلي للشكوى")).toBeInTheDocument();
    expect(apiFetch).toHaveBeenCalledWith("/provider/complains/1", {
      method: "GET",
      successCase: expect.any(Function),
      errorCase: expect.any(Function),
    });
  });

  it("clears the id query param when closing a deep-linked complaint modal", async () => {
    const user = userEvent.setup();
    searchParamsState.id = "1";
    mockComplaintsResponses();

    render(<ComplaintsPage />);
    await screen.findByText("تفاصيل الشكوى");

    await user.click(screen.getByRole("button", { name: "إغلاق النافذة" }));

    expect(replace).toHaveBeenCalledWith("/complaints");
  });

  it("refetches the first page after a successful create while already on page 1", async () => {
    const user = userEvent.setup();
    mockComplaintsResponses({
      list: buildComplaintsListData({
        data: [buildComplaint({ id: 1, title: "شكوى قديمة" })],
      }),
    });

    render(<ComplaintsPage />);
    await screen.findByRole("button", { name: /شكوى قديمة/i });

    await user.click(screen.getByRole("button", { name: "إضافة شكوى" }));
    await fillValidComplaintForm(user);
    await user.click(screen.getByRole("button", { name: "إرسال الشكوى" }));

    await waitFor(() => {
      const listCalls = vi
        .mocked(apiFetch)
        .mock.calls.filter(
          ([url, options]) =>
            typeof url === "string" &&
            url.startsWith("/provider/complains?page=1&perPage=15") &&
            options?.method === "GET",
        );
      expect(listCalls.length).toBeGreaterThanOrEqual(2);
    });
  });

  it("keeps the current list when a create-triggered refetch has no data", async () => {
    const user = userEvent.setup();
    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (url === "/provider/complains?page=1&perPage=15") {
        const envelope = {
          key: "success",
          data: buildComplaintsListData(),
          msg: "",
        } satisfies ApiEnvelope<ComplaintsListData>;
        const listCalls = vi
          .mocked(apiFetch)
          .mock.calls.filter(([calledUrl]) => calledUrl === url);
        if (listCalls.length > 1) {
          const noData = { key: "success", msg: "" } satisfies ApiEnvelope;
          options?.successCase?.(noData);
          return noData;
        }
        options?.successCase?.(envelope);
        return envelope;
      }

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

    render(<ComplaintsPage />);
    await screen.findByRole("button", { name: /تأخر في الصيانة/i });
    await user.click(screen.getByRole("button", { name: "إضافة شكوى" }));
    await fillValidComplaintForm(user);
    await user.click(screen.getByRole("button", { name: "إرسال الشكوى" }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledTimes(3);
    });
    expect(screen.getByText("رقم الشكوى: CMP-1001")).toBeInTheDocument();
  });

  it("returns to page 1 after a successful create while on a later page", async () => {
    const user = userEvent.setup();
    mockComplaintsResponses({
      list: buildComplaintsListData({
        data: [buildComplaint({ id: 10, title: "شكوى الصفحة الثانية" })],
        pagination: {
          current_page: 2,
          last_page: 2,
          per_page: 15,
          total: 20,
        },
      }),
    });

    render(<ComplaintsPage />);
    await screen.findByText("الصفحة 1 من 2");
    await user.click(getNextPageButton());
    await screen.findByRole("button", { name: /شكوى الصفحة الثانية/i });

    await user.click(screen.getByRole("button", { name: "إضافة شكوى" }));
    await fillValidComplaintForm(user);
    await user.click(screen.getByRole("button", { name: "إرسال الشكوى" }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/complains?page=1&perPage=15",
        expect.objectContaining({ method: "GET" }),
      );
    });
    expect(await screen.findByText("الصفحة 1 من 2")).toBeInTheDocument();
  });

  it("ignores stale list callbacks after unmount", async () => {
    let resolveFetch!: (envelope: ApiEnvelope<ComplaintsListData>) => void;
    let successCase!: (envelope: ApiEnvelope<ComplaintsListData>) => void;
    let errorCase!: (envelope: ApiEnvelope) => void;
    vi.mocked(apiFetch).mockImplementation((_url, options) =>
      new Promise((resolve) => {
        resolveFetch = resolve;
        successCase = options?.successCase as (envelope: ApiEnvelope<ComplaintsListData>) => void;
        errorCase = options?.errorCase as (envelope: ApiEnvelope) => void;
      }),
    );

    const { unmount } = render(<ComplaintsPage />);
    await waitFor(() => expect(successCase).toBeDefined());
    unmount();
    successCase({ key: "success", data: buildComplaintsListData() });
    errorCase({ key: "fail", msg: "خطأ متأخر" });
    resolveFetch({ key: "success" });

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