import { beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import SettleableRidesTable, {
  type SettleableRide,
  type SettlementsPagination,
} from "@/components/settlements/SettleableRidesTable";
import type { ApiEnvelope } from "@/lib/api/types";

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

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

type SettleableRidesData = {
  summary: {
    total_revenue: number;
    total_share: number;
    profit_rate: number;
    currency: string;
  };
  rides: SettleableRide[];
  pagination: SettlementsPagination;
};

function buildRide(id: number): SettleableRide {
  return {
    id,
    scooter_code: `SC-${id}`,
    ride_cost: 100,
    share_amount: 50,
    ended_at: "2026-01-01T12:00:00Z",
    start_address: "Start",
    end_address: "End",
  };
}

function buildEnvelope(
  pagination: SettlementsPagination,
  rides: SettleableRide[] = [buildRide(1)],
  currency: string | undefined = "SAR",
): ApiEnvelope<SettleableRidesData> {
  return {
    key: "success",
    data: {
      summary: {
        total_revenue: 1000,
        total_share: 500,
        profit_rate: 50,
        currency: currency as string,
      },
      rides,
      pagination,
    },
    msg: "",
  };
}

function mockSettleableResponse(
  pagination: SettlementsPagination,
  rides: SettleableRide[] = [buildRide(1)],
) {
  vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
    const envelope = buildEnvelope(pagination, rides);
    options?.successCase?.(envelope);
    return envelope;
  });
}

function getPager() {
  return screen.getByLabelText("الصفحة السابقة").parentElement!;
}

function getPageButtons() {
  return within(getPager()).getAllByRole("button", {
    name: /^(\d+|الصفحة السابقة|الصفحة التالية)$/,
  });
}

function getNumericPageButtons() {
  return within(getPager())
    .getAllByRole("button")
    .filter((button) => /^\d+$/.test(button.textContent ?? ""));
}

function getEllipsisCount() {
  return within(getPager()).queryAllByText("...").length;
}

describe("SettleableRidesTable pagination", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(formatApiMsg).mockImplementation((msg) =>
      Array.isArray(msg) ? msg.join(" ") : String(msg ?? ""),
    );
  });

  it("shows all page controls without ellipsis on a low page count", async () => {
    mockSettleableResponse({
      current_page: 1,
      last_page: 5,
      per_page: 15,
      total: 75,
    });

    render(<SettleableRidesTable />);

    await screen.findByText("75 رحلة");

    expect(getNumericPageButtons().map((button) => button.textContent)).toEqual([
      "1",
      "2",
      "3",
      "4",
      "5",
    ]);
    expect(getEllipsisCount()).toBe(0);
  });

  it("shows the beginning range for a large page count", async () => {
    mockSettleableResponse({
      current_page: 1,
      last_page: 10,
      per_page: 15,
      total: 150,
    });

    render(<SettleableRidesTable />);
    await screen.findByText("150 رحلة");

    expect(getNumericPageButtons().map((button) => button.textContent)).toEqual([
      "1",
      "2",
      "3",
      "4",
      "5",
      "10",
    ]);
    expect(getEllipsisCount()).toBe(1);
  });

  it("shows the middle range for a large page count", async () => {
    mockSettleableResponse({
      current_page: 5,
      last_page: 10,
      per_page: 15,
      total: 150,
    });

    render(<SettleableRidesTable />);
    await screen.findByText("150 رحلة");

    expect(getNumericPageButtons().map((button) => button.textContent)).toEqual([
      "1",
      "4",
      "5",
      "6",
      "10",
    ]);
    expect(getEllipsisCount()).toBe(2);
  });

  it("shows the end range for a large page count", async () => {
    mockSettleableResponse({
      current_page: 10,
      last_page: 10,
      per_page: 15,
      total: 150,
    });

    render(<SettleableRidesTable />);
    await screen.findByText("150 رحلة");

    expect(getNumericPageButtons().map((button) => button.textContent)).toEqual([
      "1",
      "6",
      "7",
      "8",
      "9",
      "10",
    ]);
    expect(getEllipsisCount()).toBe(1);
  });

  it("highlights the current page control", async () => {
    mockSettleableResponse({
      current_page: 5,
      last_page: 10,
      per_page: 15,
      total: 150,
    });

    render(<SettleableRidesTable />);
    await screen.findByText("150 رحلة");

    const currentPage = within(getPager()).getByRole("button", { name: "5" });
    expect(currentPage.className).toContain("bg-[#FCD704]");
  });

  it("invokes page change when a numeric page is selected", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      const page = Number(new URL(`http://local${url}`).searchParams.get("page"));
      const envelope = buildEnvelope({
        current_page: page,
        last_page: 10,
        per_page: 15,
        total: 150,
      });
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable />);
    await screen.findByText("150 رحلة");

    await user.click(within(getPager()).getByRole("button", { name: "4" }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith(
        expect.stringContaining("page=4"),
        expect.any(Object),
      );
    });
  });

  it("disables previous on the first page and moves back when enabled", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      const page = Number(new URL(`http://local${url}`).searchParams.get("page"));
      const envelope = buildEnvelope({
        current_page: page,
        last_page: 10,
        per_page: 15,
        total: 150,
      });
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable />);
    await screen.findByText("150 رحلة");

    const previous = screen.getByLabelText("الصفحة السابقة");
    expect(previous).toBeDisabled();

    await user.click(within(getPager()).getByRole("button", { name: "3" }));

    await waitFor(() => {
      expect(screen.getByLabelText("الصفحة السابقة")).not.toBeDisabled();
    });

    await user.click(screen.getByLabelText("الصفحة السابقة"));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith(
        expect.stringContaining("page=2"),
        expect.any(Object),
      );
    });
  });

  it("disables next on the last page and moves forward when enabled", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      const page = Number(new URL(`http://local${url}`).searchParams.get("page"));
      const envelope = buildEnvelope({
        current_page: page,
        last_page: 10,
        per_page: 15,
        total: 150,
      });
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable />);
    await screen.findByText("150 رحلة");

    const next = screen.getByLabelText("الصفحة التالية");
    expect(next).not.toBeDisabled();

    await user.click(within(getPager()).getByRole("button", { name: "10" }));

    await waitFor(() => {
      expect(screen.getByLabelText("الصفحة التالية")).toBeDisabled();
    });

    await user.click(within(getPager()).getByRole("button", { name: "8" }));
    await waitFor(() => {
      expect(screen.getByLabelText("الصفحة التالية")).not.toBeDisabled();
    });

    await user.click(screen.getByLabelText("الصفحة التالية"));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith(
        expect.stringContaining("page=9"),
        expect.any(Object),
      );
    });
  });

  it("still renders the empty-state message when no rides are returned", async () => {
    mockSettleableResponse(
      {
        current_page: 1,
        last_page: 3,
        per_page: 15,
        total: 0,
      },
      [],
    );

    render(<SettleableRidesTable />);

    expect(
      await screen.findByText("لا توجد رحلات قابلة للتسوية حالياً"),
    ).toBeInTheDocument();
  });

  it("shows an API error instead of the table", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope = { key: "fail", msg: "تعذّر تحميل الرحلات" } as const;
      options?.errorCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable />);

    expect(await screen.findByText("تعذّر تحميل الرحلات")).toBeInTheDocument();
    expect(screen.queryByRole("table")).not.toBeInTheDocument();
  });

  it("uses the default error when the API message cannot be formatted", async () => {
    vi.mocked(formatApiMsg).mockReturnValue("");
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope = { key: "fail", msg: "رسالة خلفية" } as const;
      options?.errorCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable />);

    expect(
      await screen.findByText("تعذّر تحميل الرحلات القابلة للتسوية"),
    ).toBeInTheDocument();
  });

  it("formats ride amounts when the summary is missing", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope = buildEnvelope(
        { current_page: 1, last_page: 1, per_page: 15, total: 1 },
        [buildRide(1)],
      );
      const data = envelope.data!;
      envelope.data = {
        rides: data.rides,
        pagination: data.pagination,
        summary: undefined as unknown as SettleableRidesData["summary"],
      };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable />);

    expect(await screen.findByText("SC-1")).toBeInTheDocument();
    expect(screen.getByText((content) => content.trim() === "100")).toBeInTheDocument();
    expect(screen.getByText((content) => content.trim() === "50")).toBeInTheDocument();
  });

  it("renders address and React-key fallbacks for incomplete rides", async () => {
    const incompleteRide = {
      ...buildRide(1),
      id: undefined,
      scooter_code: "SC-FALLBACK",
      start_address: "",
      end_address: "",
    } as unknown as SettleableRide;
    const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
    mockSettleableResponse(
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
      [incompleteRide],
    );

    render(<SettleableRidesTable />);

    expect(await screen.findByText("SC-FALLBACK")).toBeInTheDocument();
    expect(screen.getAllByText("—")).toHaveLength(2);
    expect(consoleError).not.toHaveBeenCalled();
    consoleError.mockRestore();
  });

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

    const { unmount } = render(<SettleableRidesTable />);
    await waitFor(() => expect(successCase).toBeDefined());
    unmount();
    successCase(buildEnvelope(
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
    ));
    errorCase({ key: "fail", msg: "خطأ متأخر" });
    resolveFetch({ key: "success" });
    expect(screen.queryByText("خطأ متأخر")).not.toBeInTheDocument();

    mockSettleableResponse(
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
      [buildRide(1)],
    );
    render(<SettleableRidesTable />);
    await screen.findByText("SC-1");
  });

  it("disables the settlement request button when there are no settleable rides", async () => {
    mockSettleableResponse(
      {
        current_page: 1,
        last_page: 1,
        per_page: 15,
        total: 0,
      },
      [],
    );

    render(<SettleableRidesTable />);

    expect(
      await screen.findByText("لا توجد رحلات قابلة للتسوية حالياً"),
    ).toBeInTheDocument();
    expect(screen.getByRole("button", { name: /طلب تسوية/ })).toBeDisabled();
  });

  it("submits a settlement request and refreshes data on success", async () => {
    const user = userEvent.setup();
    const onSettlementRequested = vi.fn();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (url === "/provider/settlements") {
        options?.successCase?.({ key: "success", msg: "تم إرسال الطلب" });
        return { key: "success", msg: "تم إرسال الطلب" };
      }

      const envelope = buildEnvelope({
        current_page: 1,
        last_page: 1,
        per_page: 15,
        total: 2,
      });
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable onSettlementRequested={onSettlementRequested} />);
    await screen.findByText("SC-1");

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

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith("/provider/settlements", {
        method: "POST",
        getSuccess: true,
        successCase: expect.any(Function),
        errorCase: expect.any(Function),
      });
    });
    expect(await screen.findByText("تم إرسال الطلب")).toBeInTheDocument();
    expect(onSettlementRequested).toHaveBeenCalledTimes(1);
  });

  it("shows an error when settlement request fails", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (url === "/provider/settlements") {
        const envelope = { key: "fail", msg: "لا يمكن إرسال طلب التسوية" } as const;
        options?.errorCase?.(envelope);
        return envelope;
      }

      const envelope = buildEnvelope({
        current_page: 1,
        last_page: 1,
        per_page: 15,
        total: 2,
      });
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<SettleableRidesTable />);
    await screen.findByText("SC-1");

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

    expect(
      await screen.findByText("لا يمكن إرسال طلب التسوية"),
    ).toBeInTheDocument();
  });
});
