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

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

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

interface SettlementsData {
  profit_rate?: number | string;
  summary?: {
    profit_rate?: number | string;
  };
  settlements: Settlement[];
  pagination: SettlementsPagination;
}

function buildSettlement(
  overrides: Partial<Settlement> = {},
): Settlement {
  return {
    id: 101,
    status: "pending",
    status_label: "قيد المراجعة",
    rides_count: 12,
    total_revenue: 5000,
    total_amount: 900,
    profit_rate: 18,
    requested_at: "2026-02-01T10:30:00.000Z",
    reviewed_at: null,
    ...overrides,
  };
}

function buildEnvelope(
  settlements: Settlement[],
  pagination: SettlementsPagination,
  profit_rate?: number | string,
): ApiEnvelope<SettlementsData> {
  return {
    key: "success",
    data: {
      settlements,
      pagination,
      summary: profit_rate === undefined ? undefined : { profit_rate },
      profit_rate,
    },
    msg: "",
  };
}

function mockSettlementsSuccess(
  settlements: Settlement[],
  pagination: SettlementsPagination,
) {
  vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
    const envelope = buildEnvelope(settlements, pagination);
    options?.successCase?.(envelope);
    return envelope;
  });
}

function mockSettlementsError(message = "خطأ في التحميل") {
  vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
    const envelope = {
      key: "fail",
      data: undefined,
      msg: message,
    } satisfies ApiEnvelope<SettlementsData>;
    options?.errorCase?.(envelope);
    return envelope;
  });
}

describe("SettlementsTable", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(apiFetch).mockResolvedValue({
      key: "success",
      data: null,
      msg: "",
    });
  });

  it("shows the loading skeleton on the initial fetch", async () => {
    vi.mocked(apiFetch).mockImplementation(() => new Promise(() => {}));

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

    expect(screen.getByText("سجل التسويات")).toBeInTheDocument();
    expect(container.querySelectorAll(".animate-pulse").length).toBeGreaterThan(0);
    expect(apiFetch).toHaveBeenCalledWith(
      expect.stringContaining("/provider/settlements?"),
      expect.objectContaining({ method: "GET" }),
    );
  });

  it("renders settlement rows with formatted amounts and detail links", async () => {
    mockSettlementsSuccess(
      [
        buildSettlement({
          id: 55,
          total_amount: 1200,
          total_revenue: 8000,
          rides_count: 20,
          status_label: "قيد المراجعة",
        }),
      ],
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
    );

    render(<SettlementsTable profitRate={18} />);

    const link = await screen.findByRole("link");
    expect(link).toHaveAttribute("href", "/settlements/details?id=55");
    expect(screen.getByText("# 55")).toBeInTheDocument();
    expect(screen.getByText("1,200 ريال")).toBeInTheDocument();
    expect(screen.getByText(/من أصل 8,000 ريال/)).toBeInTheDocument();
    expect(screen.getByText("20")).toBeInTheDocument();
    expect(screen.getByText("● قيد المراجعة")).toBeInTheDocument();
    expect(screen.getByText("المبلغ (18%)")).toBeInTheDocument();
  });

  it("prefers the live profit_rate prop over stale settlement rows", async () => {
    mockSettlementsSuccess(
      [buildSettlement({ profit_rate: 18 })],
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
    );

    render(<SettlementsTable profitRate={25} />);

    expect(await screen.findByText("المبلغ (25%)")).toBeInTheDocument();
  });

  it("uses summary.profit_rate from the settlements API when no prop is provided", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope = buildEnvelope(
        [buildSettlement({ profit_rate: 18 })],
        { current_page: 1, last_page: 1, per_page: 15, total: 1 },
        25,
      );
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<SettlementsTable />);

    expect(await screen.findByText("المبلغ (25%)")).toBeInTheDocument();
  });

  it.each([
    ["pending", "قيد المراجعة", "text-blue-400"],
    ["accepted", "تم التحويل", "text-green-400"],
    ["rejected", "مرفوض", "text-red-400"],
  ] as const)(
    "renders the %s settlement status badge",
    async (status, statusLabel, expectedClass) => {
      mockSettlementsSuccess(
        [buildSettlement({ status, status_label: statusLabel })],
        { current_page: 1, last_page: 1, per_page: 15, total: 1 },
      );

      render(<SettlementsTable />);

      const badge = await screen.findByText(`● ${statusLabel}`);
      expect(badge.className).toContain(expectedClass);
    },
  );

  it("renders the empty filter state when there are no settlements", async () => {
    mockSettlementsSuccess(
      [],
      { current_page: 1, last_page: 1, per_page: 15, total: 0 },
    );

    render(<SettlementsTable />);

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

  it("renders the error state when the fetch fails", async () => {
    mockSettlementsError("تعذّر تحميل البيانات");

    render(<SettlementsTable />);

    expect(await screen.findByText("تعذّر تحميل البيانات")).toBeInTheDocument();
    expect(screen.queryByText("سجل التسويات")).not.toBeInTheDocument();
  });

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

    render(<SettlementsTable />);

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

  it("applies status filters and resets to page one", async () => {
    const user = userEvent.setup();
    mockSettlementsSuccess(
      [buildSettlement()],
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
    );

    render(<SettlementsTable />);
    await screen.findByText("# 101");

    mockSettlementsSuccess(
      [buildSettlement({ id: 202, status: "accepted", status_label: "تم التحويل" })],
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
    );

    await user.click(screen.getByRole("button", { name: "تم التحويل" }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith(
        expect.stringMatching(/status=accepted/),
        expect.any(Object),
      );
    });
    expect(await screen.findByText("# 202")).toBeInTheDocument();
  });

  it("renders pagination controls and changes pages", async () => {
    const user = userEvent.setup();
    mockSettlementsSuccess(
      [buildSettlement({ id: 1 })],
      { current_page: 1, last_page: 2, per_page: 15, total: 30 },
    );

    render(<SettlementsTable />);
    await screen.findByText("30 تسوية");

    mockSettlementsSuccess(
      [buildSettlement({ id: 2 })],
      { current_page: 2, last_page: 2, per_page: 15, total: 30 },
    );

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

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith(
        expect.stringMatching(/page=2/),
        expect.any(Object),
      );
    });
    expect(await screen.findByText("# 2")).toBeInTheDocument();
  });

  it("navigates to the previous and next pagination pages", async () => {
    const user = userEvent.setup();
    mockSettlementsSuccess(
      [buildSettlement({ id: 1 })],
      { current_page: 1, last_page: 2, per_page: 15, total: 30 },
    );

    render(<SettlementsTable />);
    await screen.findByText("30 تسوية");

    mockSettlementsSuccess(
      [buildSettlement({ id: 2 })],
      { current_page: 2, last_page: 2, per_page: 15, total: 30 },
    );
    await user.click(screen.getByRole("button", { name: "2" }));
    await screen.findByText("# 2");

    mockSettlementsSuccess(
      [buildSettlement({ id: 9 })],
      { current_page: 1, last_page: 2, per_page: 15, total: 30 },
    );
    const prevButton = screen
      .getAllByRole("button")
      .find((button) => button.querySelector(".ri-arrow-left-s-line"));
    expect(prevButton).toBeDefined();
    await user.click(prevButton!);

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith(
        expect.stringMatching(/page=1/),
        expect.any(Object),
      );
    });
    expect(await screen.findByText("# 9")).toBeInTheDocument();

    mockSettlementsSuccess(
      [buildSettlement({ id: 3 })],
      { current_page: 2, last_page: 2, per_page: 15, total: 30 },
    );
    const nextButton = screen
      .getAllByRole("button")
      .find((button) => button.querySelector(".ri-arrow-right-s-line"));
    expect(nextButton).toBeDefined();
    await user.click(nextButton!);

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith(
        expect.stringMatching(/page=2/),
        expect.any(Object),
      );
    });
    expect(await screen.findByText("# 3")).toBeInTheDocument();
  });

  it("applies the default settlement status class for unknown statuses", async () => {
    mockSettlementsSuccess(
      [
        buildSettlement({
          status: "archived" as Settlement["status"],
          status_label: "مؤرشف",
        }),
      ],
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
    );

    render(<SettlementsTable />);

    const badge = await screen.findByText("● مؤرشف");
    expect(badge.className).toContain("text-gray-400");
    expect(badge.className).toContain("bg-white/5");
  });

  it("shows a loading overlay when refetching with existing data", async () => {
    mockSettlementsSuccess(
      [buildSettlement()],
      { current_page: 1, last_page: 1, per_page: 15, total: 1 },
    );

    const user = userEvent.setup();
    render(<SettlementsTable />);
    await screen.findByText("# 101");

    vi.mocked(apiFetch).mockImplementation(() => new Promise(() => {}));
    await user.click(screen.getByRole("button", { name: "مرفوض" }));

    expect(document.querySelector(".ri-loader-4-line.animate-spin")).toBeInTheDocument();
    expect(screen.getByText("# 101")).toBeInTheDocument();
  });

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

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

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