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

const push = vi.fn();
const getSearchParam = vi.fn();

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

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 SettlementDetails extends Settlement {
  currency: string;
  rides: SettleableRide[];
}

function buildRide(overrides: Partial<SettleableRide> = {}): SettleableRide {
  return {
    id: 501,
    scooter_code: "SC-001",
    ride_cost: 40,
    share_amount: 10,
    ended_at: "2026-02-01T14:30:00.000Z",
    start_address: "شارع الملك فهد",
    end_address: "حي العليا",
    ...overrides,
  };
}

function buildSettlementDetails(
  overrides: Partial<SettlementDetails> = {},
): SettlementDetails {
  return {
    id: 101,
    status: "pending",
    status_label: "قيد المراجعة",
    rides_count: 2,
    total_revenue: 5000,
    total_amount: 900,
    profit_rate: 18,
    requested_at: "2026-02-01T10:30:00.000Z",
    reviewed_at: null,
    currency: "SAR",
    rides: [
      buildRide(),
      buildRide({
        id: 502,
        scooter_code: "SC-002",
        ride_cost: 55,
        share_amount: 12,
      }),
    ],
    ...overrides,
  };
}

function mockSettlementDetailsFetch({
  data = buildSettlementDetails(),
  error,
}: {
  data?: SettlementDetails;
  error?: string;
} = {}) {
  vi.mocked(apiFetch).mockImplementation(async (url, options) => {
    if (typeof url === "string" && url.includes("/provider/settlements/")) {
      if (error) {
        const envelope = {
          key: "fail",
          msg: error,
        } satisfies ApiEnvelope;
        options?.errorCase?.(envelope);
        return envelope;
      }

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

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

describe("SettlementDetailsPage", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    getSearchParam.mockReturnValue("101");
  });

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

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

    expect(
      screen.getByRole("heading", { name: "تفاصيل التسوية" }),
    ).toBeInTheDocument();
    expect(container.querySelectorAll(".animate-pulse").length).toBeGreaterThan(0);
    expect(apiFetch).toHaveBeenCalledWith("/provider/settlements/101", {
      method: "GET",
      successCase: expect.any(Function),
      errorCase: expect.any(Function),
    });
  });

  it("renders settlement summary and ride table rows on success", async () => {
    mockSettlementDetailsFetch();

    render(<SettlementDetailsPage />);

    expect(
      await screen.findByText("تفاصيل التسوية #101"),
    ).toBeInTheDocument();
    expect(screen.getByText("● قيد المراجعة")).toBeInTheDocument();
    expect(screen.getByText("900 ريال")).toBeInTheDocument();
    expect(screen.getByText("5,000 ريال")).toBeInTheDocument();
    expect(screen.getByText("18%")).toBeInTheDocument();
    expect(screen.getByText("2 رحلة")).toBeInTheDocument();
    expect(screen.getByText("الرحلات المشمولة في التسوية")).toBeInTheDocument();
    expect(screen.getByText("# 501")).toBeInTheDocument();
    expect(screen.getByText("SC-001")).toBeInTheDocument();
    expect(screen.getByText("SC-002")).toBeInTheDocument();
    expect(screen.getAllByText("شارع الملك فهد").length).toBeGreaterThan(0);
    expect(screen.getAllByText("حي العليا").length).toBeGreaterThan(0);
    expect(
      screen.getByRole("link", { name: "" }),
    ).toHaveAttribute("href", "/settlements");
  });

  it("shows the error state when the settlement request fails", async () => {
    mockSettlementDetailsFetch({ error: "تعذّر تحميل تفاصيل التسوية" });

    render(<SettlementDetailsPage />);

    expect(
      await screen.findByText("تعذّر تحميل تفاصيل التسوية"),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: "العودة للتسويات" }),
    ).toBeInTheDocument();
  });

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

    render(<SettlementDetailsPage />);

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

  it("shows the missing id error without calling the API", async () => {
    getSearchParam.mockReturnValue(null);

    render(<SettlementDetailsPage />);

    expect(
      await screen.findByText("لم يتم توفير رقم التسوية"),
    ).toBeInTheDocument();
    expect(apiFetch).not.toHaveBeenCalled();
  });

  it("shows the empty rides state when settlement has no rides", async () => {
    mockSettlementDetailsFetch({
      data: buildSettlementDetails({ rides: [], rides_count: 0 }),
    });

    render(<SettlementDetailsPage />);

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

  it("uses address and list-key fallbacks for incomplete rides", async () => {
    const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
    mockSettlementDetailsFetch({
      data: buildSettlementDetails({
        rides: [
          buildRide({
            id: undefined as unknown as number,
            scooter_code: "SC-FALLBACK",
            start_address: "",
            end_address: "",
          }),
        ],
      }),
    });

    render(<SettlementDetailsPage />);

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

  it("navigates back to settlements when the error action is clicked", async () => {
    const user = userEvent.setup();
    mockSettlementDetailsFetch({ error: "تعذّر تحميل تفاصيل التسوية" });

    render(<SettlementDetailsPage />);
    await screen.findByText("تعذّر تحميل تفاصيل التسوية");

    await user.click(screen.getByRole("button", { name: "العودة للتسويات" }));

    expect(push).toHaveBeenCalledWith("/settlements");
  });

  it("renders rejection reason and transfer proof actions for status-specific settlements", async () => {
    mockSettlementDetailsFetch({
      data: buildSettlementDetails({
        status: "rejected",
        status_label: "مرفوض",
        rejection_reason: "بيانات الحساب غير مكتملة",
        reviewed_at: "2026-02-02T09:00:00.000Z",
      }),
    });

    const { unmount } = render(<SettlementDetailsPage />);

    expect(await screen.findByText("سبب الرفض")).toBeInTheDocument();
    expect(screen.getByText("بيانات الحساب غير مكتملة")).toBeInTheDocument();
    unmount();

    mockSettlementDetailsFetch({
      data: buildSettlementDetails({
        status: "accepted",
        status_label: "تم التحويل",
        transfer_proof_url: "https://example.com/proof.pdf",
        reviewed_at: "2026-02-02T09:00:00.000Z",
      }),
    });

    render(<SettlementDetailsPage />);

    const receiptLink = await screen.findByRole("link", { name: "عرض الإيصال" });
    expect(receiptLink).toHaveAttribute("href", "https://example.com/proof.pdf");
    expect(receiptLink).toHaveAttribute("target", "_blank");
  });

  it("refetches when the settlement id changes", async () => {
    mockSettlementDetailsFetch();

    const { rerender } = render(<SettlementDetailsPage />);
    await screen.findByText("تفاصيل التسوية #101");

    getSearchParam.mockReturnValue("202");
    mockSettlementDetailsFetch({
      data: buildSettlementDetails({ id: 202 }),
    });

    rerender(<SettlementDetailsPage />);

    await waitFor(() => {
      expect(apiFetch).toHaveBeenLastCalledWith("/provider/settlements/202", {
        method: "GET",
        successCase: expect.any(Function),
        errorCase: expect.any(Function),
      });
    });
    expect(await screen.findByText("تفاصيل التسوية #202")).toBeInTheDocument();
  });

  it("renders default status styling for unknown settlement statuses", async () => {
    mockSettlementDetailsFetch({
      data: buildSettlementDetails({
        status: "archived" as Settlement["status"],
        status_label: "مؤرشف",
      }),
    });

    render(<SettlementDetailsPage />);

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

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

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

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