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 HomePage from "@/app/page";
import type { HomeData } from "@/app/page";
import type { ApiEnvelope } from "@/lib/api/types";
import type { TransactionsData } from "@/lib/transactions";

vi.mock("@/components/DashboardLayout", () => ({
  default: ({
    children,
    title,
  }: {
    children: React.ReactNode;
    title: string;
  }) => (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  ),
}));

vi.mock("@/components/home/EarningsChart", () => ({
  default: ({
    bars,
    rangeMonths,
  }: {
    bars: { label: string }[];
    rangeMonths: number;
  }) => (
    <div data-testid="earnings-chart">
      {rangeMonths}-{bars.map((bar) => bar.label).join(",")}
    </div>
  ),
}));

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

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

function buildHomeData(overrides: Partial<HomeData> = {}): HomeData {
  return {
    currency: "SAR",
    cards: {
      trips_today: { value: 42, avg_per_vehicle: 3 },
      revenue_today: {
        value: 5000,
        your_share: 1250,
        change: { formatted: "+8%", trend: "up", label: "عن أمس" },
      },
      vehicles: {
        total: 10,
        active: 8,
        maintenance: 1,
        in_ride: 2,
        inactive: 1,
      },
      total_profits: {
        value: 50000,
        change: { formatted: "+15%", trend: "up", label: "هذا الشهر" },
      },
    },
    profit_share: {
      percent: 25,
      subtitle: "نسبة ثابتة من كل رحلة",
      expected_profit_month: 3000,
      expected_profit_month_label: "الربح المتوقع هذا الشهر",
    },
    monthly_profits_chart: {
      range_months: 6,
      bars: [
        { label: "يناير", profit: 1000, is_current: false },
        { label: "فبراير", profit: 1500, is_current: true },
      ],
    },
    ...overrides,
  };
}

function buildTransactionsData(
  overrides: Partial<TransactionsData> = {},
): TransactionsData {
  return {
    currency: "SAR",
    transactions: [
      {
        scooter_code: "SC-101",
        area: "الرياض - العليا",
        rides_count: 5,
        revenue: 200,
        your_share: 50,
        relative_time: "منذ ساعتين",
        last_ride_at: "2026-07-14T10:00:00Z",
      },
    ],
    pagination: {
      current_page: 1,
      last_page: 1,
      per_page: 15,
      total: 1,
    },
    ...overrides,
  };
}

function mockHomeResponses({
  home = buildHomeData(),
  transactions = buildTransactionsData(),
  homeError,
  transactionsError,
  homePending = false,
  transactionsPending = false,
  homeWithoutData = false,
}: {
  home?: HomeData;
  transactions?: TransactionsData;
  homeError?: string;
  transactionsError?: string;
  homePending?: boolean;
  transactionsPending?: boolean;
  homeWithoutData?: boolean;
} = {}) {
  vi.mocked(apiFetch).mockImplementation(async (url, options) => {
    if (typeof url === "string" && url.includes("/provider/home")) {
      if (homePending) {
        return new Promise(() => {});
      }

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

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

    if (typeof url === "string" && url.includes("/provider/transactions")) {
      if (transactionsPending) {
        return new Promise(() => {});
      }

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

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

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

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

  it("renders the page title and home loading skeleton while /provider/home is pending", async () => {
    mockHomeResponses({ homePending: true });

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

    expect(
      screen.getByRole("heading", { name: "لوحة التحكم" }),
    ).toBeInTheDocument();

    const homeSection = container.querySelector(".grid.grid-cols-4.gap-5");
    expect(homeSection).toBeInTheDocument();
    expect(
      within(homeSection as HTMLElement).getAllByRole("generic").length,
    ).toBeGreaterThanOrEqual(4);
    expect(
      (homeSection as HTMLElement).querySelectorAll(".animate-pulse").length,
    ).toBeGreaterThan(0);
  });

  it("shows the home error state when the home request fails", async () => {
    mockHomeResponses({ homeError: "تعذّر تحميل لوحة التحكم" });

    render(<HomePage />);

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

  it("renders summary cards, chart, and profit banner on home success", async () => {
    mockHomeResponses();

    render(<HomePage />);

    expect(await screen.findByText("إجمالي الأرباح")).toBeInTheDocument();
    expect(screen.getByText("مركباتي")).toBeInTheDocument();
    expect(screen.getByText("إيرادات اليوم")).toBeInTheDocument();
    expect(screen.getByText("الرحلات اليوم")).toBeInTheDocument();
    expect(screen.getByTestId("earnings-chart")).toHaveTextContent(
      "6-يناير,فبراير",
    );
    expect(screen.getByText("نسبة أرباحك من النظام")).toBeInTheDocument();
    expect(screen.getByText("25%")).toBeInTheDocument();
  });

  it('shows the home empty state "لا توجد بيانات لعرضها" when home succeeds without data', async () => {
    mockHomeResponses({ homeWithoutData: true });

    render(<HomePage />);

    const emptyMessages = await screen.findAllByText("لا توجد بيانات لعرضها");
    expect(emptyMessages).toHaveLength(1);
  });

  it("renders the transactions loading skeleton while transactions are pending", async () => {
    mockHomeResponses({ transactionsPending: true });

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

    await screen.findByText("إجمالي الأرباح");

    const tableBody = container.querySelector("tbody");
    expect(tableBody).toBeInTheDocument();
    expect(
      tableBody!.querySelectorAll("tr .animate-pulse").length,
    ).toBeGreaterThanOrEqual(5);
  });

  it('shows the transactions empty state "لا توجد معاملات لعرضها"', async () => {
    mockHomeResponses({
      transactions: buildTransactionsData({ transactions: [] }),
    });

    render(<HomePage />);

    expect(
      await screen.findByText("لا توجد معاملات لعرضها"),
    ).toBeInTheDocument();
  });

  it("shows the transactions error state when the transactions request fails", async () => {
    mockHomeResponses({ transactionsError: "تعذّر تحميل المعاملات" });

    render(<HomePage />);

    expect(await screen.findByText("إجمالي الأرباح")).toBeInTheDocument();
    expect(
      await screen.findByText("تعذّر تحميل المعاملات"),
    ).toBeInTheDocument();
  });

  it("uses default errors when API messages cannot be formatted", async () => {
    vi.mocked(formatApiMsg).mockReturnValue("");
    mockHomeResponses({
      homeError: "رسالة خلفية",
      transactionsError: "رسالة خلفية",
    });

    render(<HomePage />);

    expect(
      await screen.findByText("تعذّر تحميل البيانات، حاول مرة أخرى"),
    ).toBeInTheDocument();
    expect(
      await screen.findByText("تعذّر تحميل المعاملات، حاول مرة أخرى"),
    ).toBeInTheDocument();
  });

  it("renders transaction rows with scooter code, area, and rides count", async () => {
    mockHomeResponses();

    render(<HomePage />);

    expect(await screen.findByText("SC-101")).toBeInTheDocument();
    expect(screen.getByText("الرياض - العليا")).toBeInTheDocument();
    expect(screen.getByText("5 رحلة")).toBeInTheDocument();
    expect(screen.getByText("منذ ساعتين")).toBeInTheDocument();
    expect(screen.getByText("+50 ريال")).toBeInTheDocument();
  });

  it("refetches transactions with page=2 when page 2 is clicked", async () => {
    const user = userEvent.setup();
    mockHomeResponses({
      transactions: buildTransactionsData({
        pagination: {
          current_page: 1,
          last_page: 3,
          per_page: 15,
          total: 30,
        },
      }),
    });

    render(<HomePage />);
    await screen.findByText("SC-101");

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

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/transactions?page=2&per_page=15",
        expect.objectContaining({ method: "GET" }),
      );
    });
  });

  it("navigates with prev and next pagination buttons", async () => {
    const user = userEvent.setup();
    mockHomeResponses({
      transactions: buildTransactionsData({
        pagination: {
          current_page: 1,
          last_page: 3,
          per_page: 15,
          total: 30,
        },
      }),
    });

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (typeof url === "string" && url.includes("/provider/home")) {
        const envelope = {
          key: "success",
          data: buildHomeData(),
          msg: "",
        } satisfies ApiEnvelope<HomeData>;
        options?.successCase?.(envelope);
        return envelope;
      }

      if (typeof url === "string" && url.includes("/provider/transactions")) {
        const pageMatch = url.match(/page=(\d+)/);
        const page = pageMatch ? Number(pageMatch[1]) : 1;
        const envelope = {
          key: "success",
          data: buildTransactionsData({
            pagination: {
              current_page: page,
              last_page: 3,
              per_page: 15,
              total: 30,
            },
          }),
          msg: "",
        } satisfies ApiEnvelope<TransactionsData>;
        options?.successCase?.(envelope);
        return envelope;
      }

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

    render(<HomePage />);
    await screen.findByText("30 معاملة");

    const prevButton = screen.getByRole("button", { name: "الصفحة السابقة" });
    const nextButton = screen.getByRole("button", { name: "الصفحة التالية" });

    expect(prevButton).toBeDisabled();
    expect(nextButton).not.toBeDisabled();

    await user.click(nextButton);

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/transactions?page=2&per_page=15",
        expect.objectContaining({ method: "GET" }),
      );
    });
    expect(await screen.findByRole("button", { name: "2" })).toHaveClass(
      "bg-[#FCD704]",
    );

    await user.click(nextButton);

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/transactions?page=3&per_page=15",
        expect.objectContaining({ method: "GET" }),
      );
    });
    expect(nextButton).toBeDisabled();

    await user.click(prevButton);

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/transactions?page=2&per_page=15",
        expect.objectContaining({ method: "GET" }),
      );
    });
    expect(nextButton).not.toBeDisabled();
  });

  it("ignores a stale home error callback after unmount", async () => {
    let resolveHome!: () => void;
    vi.mocked(apiFetch).mockImplementation((url, options) => {
      if (typeof url === "string" && url.includes("/provider/home")) {
        return new Promise<ApiEnvelope<HomeData>>((resolve) => {
          resolveHome = () => {
            const envelope = { key: "fail", msg: "خطأ متأخر" } satisfies ApiEnvelope;
            options?.errorCase?.(envelope);
            resolve(envelope);
          };
        });
      }
      const envelope = {
        key: "success",
        data: buildTransactionsData(),
        msg: "",
      } satisfies ApiEnvelope<TransactionsData>;
      options?.successCase?.(envelope);
      return Promise.resolve(envelope);
    });

    const { unmount } = render(<HomePage />);
    await waitFor(() => expect(apiFetch).toHaveBeenCalled());
    unmount();
    resolveHome();

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

  it("ignores every stale home and transaction callback after unmount", async () => {
    const callbacks: Array<{
      success?: (envelope: ApiEnvelope<unknown>) => void;
      error?: (envelope: ApiEnvelope) => void;
      resolve: (envelope: ApiEnvelope<unknown>) => void;
    }> = [];
    vi.mocked(apiFetch).mockImplementation((_url, options) =>
      new Promise((resolve) => {
        callbacks.push({
          success: options?.successCase as (envelope: ApiEnvelope<unknown>) => void,
          error: options?.errorCase,
          resolve,
        });
      }),
    );

    const { unmount } = render(<HomePage />);
    await waitFor(() => expect(callbacks).toHaveLength(2));
    unmount();

    callbacks.forEach(({ success, error, resolve }, index) => {
      success?.({ key: "success", data: index ? buildTransactionsData() : buildHomeData() });
      error?.({ key: "fail", msg: "خطأ متأخر" });
      resolve({ key: "success" });
    });

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