import { beforeEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import ProfilePage, { type ProfileData } from "@/app/profile/page";
import type { ApiEnvelope } from "@/lib/api/types";

const { replace, performLogout } = vi.hoisted(() => ({
  replace: vi.fn(),
  performLogout: vi.fn(async (redirect: (path: string) => void) => {
    redirect("/login");
  }),
}));

vi.mock("next/navigation", () => ({
  useRouter: () => ({ replace, push: vi.fn() }),
}));

vi.mock("@/lib/auth/logout", () => ({
  performLogout,
}));

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";

function buildProfileData(overrides: Partial<ProfileData> = {}): ProfileData {
  return {
    name: "أحمد سعيد",
    email: "ahmed@beepbeep.sa",
    phone: "512345678",
    country_code: "+966",
    national_id: "••••••••90",
    national_id_full: "1234567890",
    iban: "SA••••••••",
    iban_full: "SA0380000000608010167519",
    avatar_initials: "أس",
    investor_since: "مستثمر منذ يونيو 2026",
    membership_months: 6,
    profit_rate_label: "18% نسبة الأرباح",
    vehicle_count: "3-5",
    vehicles_count: 4,
    has_pending_profile_update: false,
    is_approved: true,
    is_verified: true,
    dashboard_preview: {
      rides_today: 12,
      profit_share_percent: 18,
      active_vehicles: 3,
    },
    ...overrides,
  };
}

function mockProfileFetch({
  profile = buildProfileData(),
  loadError,
  submitError,
  submitSuccessMsg = "تم إرسال طلب التحديث",
    submitNeedApprove,
    submitNeedApproveMsg,
    submitSuccessEmptyMsg,
}: {
  profile?: ProfileData;
  loadError?: string;
  submitError?: string;
  submitSuccessMsg?: string;
  submitNeedApprove?: boolean;
  submitNeedApproveMsg?: string;
  submitSuccessEmptyMsg?: boolean;
} = {}) {
  vi.mocked(apiFetch).mockImplementation(async (url, options) => {
    if (url === "/provider/profile") {
      if (loadError) {
        const envelope = {
          key: "fail",
          msg: loadError,
        } satisfies ApiEnvelope;
        options?.errorCase?.(envelope);
        return envelope;
      }

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

    if (url === "/provider/update/profile") {
      if (submitNeedApprove) {
        const envelope = {
          key: "needApprove",
          msg: submitNeedApproveMsg ?? "",
        } satisfies ApiEnvelope;
        options?.needApproveCase?.(envelope);
        return envelope;
      }

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

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

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

function getNameInput() {
  return screen.getByPlaceholderText("الاسم الكامل");
}

function getPhoneInput() {
  return screen.getByPlaceholderText("5XXXXXXXX");
}

function getCountryCodeInput() {
  return screen.getByPlaceholderText("+966");
}

function getNationalIdInput() {
  return screen.getByPlaceholderText("1XXXXXXXXX");
}

function getIbanInput() {
  return screen.getByPlaceholderText("SA00 0000 0000 0000 0000 0000");
}

describe("ProfilePage", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(formatApiMsg).mockImplementation((msg) => String(msg ?? ""));
  });

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

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

    expect(
      screen.getByRole("heading", { name: "الملف الشخصي" }),
    ).toBeInTheDocument();
    expect(container.querySelectorAll(".animate-pulse").length).toBeGreaterThan(0);
  });

  it("shows the load error state when the profile request fails", async () => {
    mockProfileFetch({ loadError: "تعذّر تحميل الملف الشخصي" });

    render(<ProfilePage />);

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

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

    render(<ProfilePage />);

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

  it("renders profile identity, stats, dashboard preview, and prefilled form on success", async () => {
    mockProfileFetch();

    render(<ProfilePage />);

    expect(await screen.findByText("أحمد سعيد")).toBeInTheDocument();
    expect(screen.getByText("مستثمر منذ يونيو 2026")).toBeInTheDocument();
    expect(screen.getByText("أس")).toBeInTheDocument();
    expect(screen.getByText("حساب موثّق ✓")).toBeInTheDocument();
    expect(screen.getByText("ahmed@beepbeep.sa")).toBeInTheDocument();
    expect(screen.getByText("4")).toBeInTheDocument();
    expect(screen.getAllByText("18% نسبة الأرباح")).toHaveLength(2);
    expect(screen.getByText("6 شهر")).toBeInTheDocument();
    expect(screen.getByText("نشاط اليوم")).toBeInTheDocument();
    expect(screen.getByText("12")).toBeInTheDocument();

    expect(getNameInput()).toHaveValue("أحمد سعيد");
    expect(getCountryCodeInput()).toHaveValue("+966");
    expect(getPhoneInput()).toHaveValue("512345678");
    expect(getNationalIdInput()).toHaveValue("1234567890");
    expect(getIbanInput()).toHaveValue("SA0380000000608010167519");
    expect(
      screen.getByRole("link", { name: "تغيير كلمة المرور" }),
    ).toHaveAttribute("href", "/change-password");
  });

  it("shows validation errors without calling the update endpoint", async () => {
    const user = userEvent.setup();
    mockProfileFetch();

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.clear(getNameInput());
    await user.clear(getPhoneInput());
    fireEvent.submit(getNameInput().closest("form")!);

    expect(await screen.findByText("الاسم مطلوب")).toBeInTheDocument();
    expect(screen.getByText("رقم الجوال مطلوب")).toBeInTheDocument();
    expect(apiFetch).toHaveBeenCalledTimes(1);
  });

  it("shows submit loading state while the update request is in flight", async () => {
    const user = userEvent.setup();
    let resolveSubmit!: (envelope: ApiEnvelope) => void;
    const submitPromise = new Promise<ApiEnvelope>((resolve) => {
      resolveSubmit = resolve;
    });

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (url === "/provider/profile") {
        const envelope = {
          key: "success",
          data: buildProfileData(),
          msg: "",
        } satisfies ApiEnvelope<ProfileData>;
        options?.successCase?.(envelope);
        return envelope;
      }

      if (url === "/provider/update/profile") {
        const envelope = await submitPromise;
        options?.successCase?.(envelope);
        return envelope;
      }

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

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

    expect(await screen.findByText("جاري الحفظ...")).toBeInTheDocument();
    resolveSubmit({ key: "success", msg: "تم الحفظ" });
    await waitFor(() => {
      expect(screen.queryByText("جاري الحفظ...")).not.toBeInTheDocument();
    });
  });

  it("shows the pending banner when update returns needApprove", async () => {
    const user = userEvent.setup();
    mockProfileFetch({
      submitNeedApprove: true,
      submitNeedApproveMsg: "بانتظار موافقة الإدارة",
    });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

    expect(
      await screen.findByText("بانتظار موافقة الإدارة"),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: /في انتظار الموافقة/i }),
    ).toBeDisabled();
  });

  it("submits profile edits and shows the pending-approval banner on success", async () => {
    const user = userEvent.setup();
    mockProfileFetch({
      submitSuccessMsg: "تم إرسال طلب التحديث بنجاح",
    });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.clear(getNameInput());
    await user.type(getNameInput(), "محمد علي");
    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith("/provider/update/profile", {
        method: "POST",
        body: JSON.stringify({
          name: "محمد علي",
          country_code: "+966",
          phone: "512345678",
          national_id: "1234567890",
          iban: "SA0380000000608010167519",
        }),
        successCase: expect.any(Function),
        needApproveCase: expect.any(Function),
        errorCase: expect.any(Function),
      });
    });

    expect(
      await screen.findByText("تم إرسال طلب التحديث بنجاح"),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: /في انتظار الموافقة/i }),
    ).toBeDisabled();
    expect(screen.getByText("أحمد سعيد")).toBeInTheDocument();
  });

  it("shows the membership placeholder when membership months are null", async () => {
    mockProfileFetch({
      profile: buildProfileData({ membership_months: undefined }),
    });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    const membershipRow = screen
      .getByText("عضوية نشطة")
      .closest(".flex.items-center.justify-between");
    expect(membershipRow).toHaveTextContent("—");
    expect(membershipRow).not.toHaveTextContent("شهر");
  });

  it("renders missing profile statistics and an unapproved account safely", async () => {
    mockProfileFetch({
      profile: buildProfileData({
        vehicles_count: undefined as unknown as number,
        profit_rate_label: undefined as unknown as string,
        avatar_initials: undefined as unknown as string,
        is_approved: false,
      }),
    });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    expect(screen.getByText("؟")).toBeInTheDocument();
    expect(screen.getByText("في انتظار الموافقة")).toBeInTheDocument();
    expect(screen.getAllByText("—")).toHaveLength(2);
  });

  it("displays phone, national-id, and IBAN validation feedback", async () => {
    const user = userEvent.setup();
    mockProfileFetch();
    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.clear(getPhoneInput());
    await user.clear(getNationalIdInput());
    await user.clear(getIbanInput());
    fireEvent.submit(getNameInput().closest("form")!);

    expect(await screen.findByText("رقم الجوال مطلوب")).toBeInTheDocument();
    expect(screen.getByText("الرجاء إدخال رقم الهوية الوطنية")).toBeInTheDocument();
    expect(screen.getByText("الرجاء إدخال رقم آيبان صحيح")).toBeInTheDocument();
  });

  it("shows the submit error when the update request fails", async () => {
    const user = userEvent.setup();
    mockProfileFetch({ submitError: "تعذّر حفظ التغييرات" });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

    expect(
      await screen.findByText("تعذّر حفظ التغييرات"),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: "حفظ التغييرات" }),
    ).not.toBeDisabled();
  });

  it("falls back to a default submit error when the update response has no msg", async () => {
    const user = userEvent.setup();
    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      if (url === "/provider/profile") {
        const envelope = {
          key: "success",
          data: buildProfileData(),
          msg: "",
        } satisfies ApiEnvelope<ProfileData>;
        options?.successCase?.(envelope);
        return envelope;
      }

      if (url === "/provider/update/profile") {
        const envelope = { key: "fail", msg: "" } satisfies ApiEnvelope;
        options?.errorCase?.(envelope);
        return envelope;
      }

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

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

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

  it("uses the default submit error when formatting returns an empty message", async () => {
    const user = userEvent.setup();
    vi.mocked(formatApiMsg).mockReturnValue("");
    mockProfileFetch({ submitError: "رسالة خلفية" });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");
    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

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

  it("shows the country-code validation error when phone is valid", async () => {
    const user = userEvent.setup();
    mockProfileFetch();
    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.clear(getCountryCodeInput());
    fireEvent.submit(getNameInput().closest("form")!);

    expect(await screen.findByText("رمز الدولة مطلوب")).toBeInTheDocument();
  });

  it("disables submit and shows the pending banner when a prior update is awaiting approval", async () => {
    mockProfileFetch({
      profile: buildProfileData({ has_pending_profile_update: true }),
    });

    render(<ProfilePage />);

    expect(
      await screen.findByText(
        "طلب تحديث سابق لا يزال في انتظار موافقة الإدارة — لا يمكن إرسال طلب آخر حالياً",
      ),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: /في انتظار الموافقة/i }),
    ).toBeDisabled();
  });

  it("uses the default pending message when update success has no msg", async () => {
    const user = userEvent.setup();
    mockProfileFetch({ submitSuccessEmptyMsg: true });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

    expect(
      await screen.findByText(
        "تم إرسال طلب التحديث، في انتظار موافقة الإدارة",
      ),
    ).toBeInTheDocument();
  });

  it("uses the default pending message when needApprove has no msg", async () => {
    const user = userEvent.setup();
    mockProfileFetch({ submitNeedApprove: true, submitNeedApproveMsg: "" });

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.click(screen.getByRole("button", { name: "حفظ التغييرات" }));

    expect(
      await screen.findByText(
        "تم إرسال طلب التحديث، في انتظار موافقة الإدارة",
      ),
    ).toBeInTheDocument();
  });

  it("calls performLogout and redirects to login on logout", async () => {
    const user = userEvent.setup();
    mockProfileFetch();

    render(<ProfilePage />);
    await screen.findByText("أحمد سعيد");

    await user.click(screen.getByRole("button", { name: "تسجيل الخروج" }));

    expect(performLogout).toHaveBeenCalledTimes(1);
    expect(replace).toHaveBeenCalledWith("/login");
  });

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

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

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