import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
  fireEvent,
  render,
  screen,
  waitFor,
  within,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import SignupForm from "@/components/auth/SignupForm";
import type { ApiEnvelope } from "@/lib/api/types";
import type { AuthInfo } from "@/lib/auth/store";
import { MAX_UPLOAD_SIZE_BYTES } from "@/lib/upload/fileUtils";

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

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

vi.mock("@/lib/auth/store", () => ({
  useAuthStore: (
    selector: (state: { setAuth: typeof setAuth }) => unknown,
  ) => selector({ setAuth }),
}));

vi.mock("@/hooks/useProviderStats", () => ({
  useProviderStats: () => ({
    stats: {
      rides_today: 124,
      profit_rate: 18,
      active_vehicles: 7,
    },
    loading: false,
  }),
}));

vi.mock("@/lib/api", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@/lib/api")>();
  return {
    ...actual,
    apiFetch: vi.fn(),
  };
});

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

const VALID_PASSWORD = "Abcd1234!";
const VALID_PHONE = "512345678";
const VALID_EMAIL = "user@example.com";
const VALID_IBAN = "SA0380000000608010167519";

const alertMock = vi.fn();

function makeFile(
  name: string,
  options: { type?: string; size?: number } = {},
): File {
  const { type = "application/pdf", size = 2048 } = options;
  const file = new File([new Uint8Array(Math.min(size, 1024))], name, { type });
  if (size !== file.size) {
    Object.defineProperty(file, "size", { value: size });
  }
  return file;
}

function getFirstNameInput() {
  return screen.getByPlaceholderText("أحمد");
}

function getLastNameInput() {
  return screen.getByPlaceholderText("المستثمر");
}

function getEmailInput() {
  return screen.getByPlaceholderText("ahmed@beepbeep.sa");
}

function getPhoneInput() {
  return screen.getByPlaceholderText("+966 5X XXX XXXX");
}

function getPasswordInput() {
  return screen.getByPlaceholderText("8 أحرف على الأقل");
}

function getConfirmPasswordInput() {
  return screen.getByPlaceholderText("أعد إدخال كلمة المرور");
}

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

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

function getNextButton() {
  return screen.getByRole("button", { name: "التالي ←" });
}

function getTermsCheckbox() {
  const checkbox = document.getElementById("terms-check");
  if (!checkbox) {
    throw new Error("Terms checkbox not found");
  }
  return checkbox;
}

async function acceptTerms(user: ReturnType<typeof userEvent.setup>) {
  await user.click(getTermsCheckbox());
}

function getCreateAccountButton() {
  return screen.getByRole("button", { name: "إنشاء الحساب" });
}

function uploadDocument(file: File) {
  const input = getDocumentFileInput();
  fireEvent.change(input, { target: { files: [file] } });
}

function getDocumentFileInput(): HTMLInputElement {
  const dropzone = screen.getByRole("button", { name: /ارفق المستندات/i });
  const input = dropzone.querySelector('input[type="file"]');
  if (!input) {
    throw new Error("Document file input not found");
  }
  return input as HTMLInputElement;
}

function mockApiSuccess(authData?: Partial<AuthInfo>) {
  vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
    const envelope: ApiEnvelope<AuthInfo> = {
      key: "success",
      msg: "",
      data: {
        id: 1,
        first_name: "أحمد",
        last_name: "المستثمر",
        name: "أحمد المستثمر",
        email: VALID_EMAIL,
        phone: VALID_PHONE,
        country_code: "966",
        full_phone: `966${VALID_PHONE}`,
        image: null,
        avatar_initials: "أم",
        is_approved: 0,
        is_verified: 0,
        is_active: 1,
        is_blocked: 0,
        is_notify: 1,
        has_vehicles: 0,
        profit_rate: 18,
        vehicles_count: 0,
        vehicle_count: 0,
        national_id: "1234567890",
        iban: VALID_IBAN,
        documents: [],
        dashboard_preview: null,
        token: "signup-token",
        preferred_locale: "ar",
        ...authData,
      },
    };
    options?.successCase?.(envelope);
    return envelope;
  });
}

function mockApiFailure(message: string) {
  vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
    const envelope: ApiEnvelope = { key: "fail", msg: message };
    options?.errorCase?.(envelope);
    return envelope;
  });
}

async function fillStepOne(
  user: ReturnType<typeof userEvent.setup>,
  overrides: {
    email?: string;
    phone?: string;
    password?: string;
    confirmation?: string;
  } = {},
) {
  await user.type(getFirstNameInput(), "أحمد");
  await user.type(getLastNameInput(), "المستثمر");
  await user.type(getEmailInput(), overrides.email ?? VALID_EMAIL);
  await user.type(getPhoneInput(), overrides.phone ?? VALID_PHONE);
  await user.type(getPasswordInput(), overrides.password ?? VALID_PASSWORD);
  await user.type(
    getConfirmPasswordInput(),
    overrides.confirmation ?? overrides.password ?? VALID_PASSWORD,
  );
}

async function advanceToStepTwo(user: ReturnType<typeof userEvent.setup>) {
  await fillStepOne(user);
  await user.click(getNextButton());
  await waitFor(() => {
    expect(screen.getByText("بيانات الاستثمار")).toBeInTheDocument();
  });
}

async function fillStepTwo(
  user: ReturnType<typeof userEvent.setup>,
  file: File = makeFile("identity.pdf"),
) {
  await user.type(getNationalIdInput(), "1234567890");
  await user.type(getIbanInput(), VALID_IBAN);
  await user.click(screen.getByRole("button", { name: "1-2" }));
  await user.upload(getDocumentFileInput(), file);
  await waitFor(() => {
    expect(screen.getByText(file.name)).toBeInTheDocument();
  });
  await acceptTerms(user);
}

function mockLegalPageSuccess(content: string) {
  vi.mocked(apiFetch).mockImplementation(async (url, options) => {
    if (url === "/terms" || url === "/privacy") {
      const envelope: ApiEnvelope<{ content: string }> = {
        key: "success",
        msg: "",
        data: { content },
      };
      options?.successCase?.(envelope);
      return envelope;
    }

    const envelope: ApiEnvelope = { key: "fail", msg: "" };
    return envelope;
  });
}

describe("SignupForm", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.stubGlobal("alert", alertMock);
  });

  afterEach(() => {
    vi.unstubAllGlobals();
  });

  describe("step 1 — basic details", () => {
    it("renders the initial signup step with required fields and next action", () => {
      render(<SignupForm />);

      expect(screen.getByText("إنشاء حساب جديد ✨")).toBeInTheDocument();
      expect(screen.getByText("البيانات الأساسية")).toBeInTheDocument();
      expect(getFirstNameInput()).toBeInTheDocument();
      expect(getLastNameInput()).toBeInTheDocument();
      expect(getEmailInput()).toBeInTheDocument();
      expect(getPhoneInput()).toBeInTheDocument();
      expect(getPasswordInput()).toBeInTheDocument();
      expect(getConfirmPasswordInput()).toBeInTheDocument();
      expect(getNextButton()).toBeInTheDocument();
      expect(
        screen.queryByRole("button", { name: "→ العودة للخطوة السابقة" }),
      ).not.toBeInTheDocument();
    });

    it("rejects empty required fields on step one", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      await user.click(getNextButton());

      expect(
        await screen.findByText("الرجاء إدخال الاسم الأول"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });

    it("rejects a malformed email on step one", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      await fillStepOne(user, { email: "abc" });
      fireEvent.submit(getEmailInput().closest("form")!);

      expect(
        await screen.findByText("يرجى إدخال بريد إلكتروني صحيح"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });

    it("rejects an invalid phone number on step one", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      await fillStepOne(user, { phone: "12345" });
      await user.click(getNextButton());

      expect(
        await screen.findByText("رقم الجوال يجب أن يكون 9 أرقام"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });

    it("rejects mismatched password confirmation on step one", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      await fillStepOne(user, { confirmation: "Mismatch1!" });
      await user.click(getNextButton());

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

    it("rejects a weak password that does not meet requirements on step one", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      await fillStepOne(user, { password: "weak", confirmation: "weak" });
      await user.click(getNextButton());

      expect(
        await screen.findByText("كلمة المرور يجب أن تكون 8 أحرف على الأقل"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });

    it("advances to step two when step-one data is valid", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      await advanceToStepTwo(user);

      expect(screen.getByText("بيانات الاستثمار")).toBeInTheDocument();
      expect(getNationalIdInput()).toBeInTheDocument();
      expect(getCreateAccountButton()).toBeInTheDocument();
      expect(
        screen.getByRole("button", { name: "→ العودة للخطوة السابقة" }),
      ).toBeInTheDocument();
    });

    it("preserves step-one data when returning from step two", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await user.click(
        screen.getByRole("button", { name: "→ العودة للخطوة السابقة" }),
      );

      expect(getEmailInput()).toHaveValue(VALID_EMAIL);
      expect(getPhoneInput()).toHaveValue(VALID_PHONE);
    });

    it("toggles password and confirmation visibility on step one", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);

      const passwordField = getPasswordInput().closest(".relative") as HTMLElement;
      const confirmField = getConfirmPasswordInput().closest(
        ".relative",
      ) as HTMLElement;

      await user.click(within(passwordField).getByRole("button"));
      expect(getPasswordInput()).toHaveAttribute("type", "text");

      await user.click(within(confirmField).getByRole("button"));
      expect(getConfirmPasswordInput()).toHaveAttribute("type", "text");
    });
  });

  describe("step 2 — investment details and uploads", () => {
    it("accepts an allowed PDF upload and displays the file", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const pdf = makeFile("contract.pdf", { type: "application/pdf" });
      await user.upload(getDocumentFileInput(), pdf);

      expect(await screen.findByText("contract.pdf")).toBeInTheDocument();
      expect(screen.getByText("2 KB")).toBeInTheDocument();
    });

    it("accepts allowed JPEG and PNG uploads", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const jpeg = makeFile("photo.jpg", { type: "image/jpeg" });
      const png = makeFile("scan.png", { type: "image/png" });

      await user.upload(getDocumentFileInput(), jpeg);
      await user.upload(getDocumentFileInput(), png);

      expect(screen.getByText("photo.jpg")).toBeInTheDocument();
      expect(screen.getByText("scan.png")).toBeInTheDocument();
    });

    it("rejects a disallowed file type with an alert", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const textFile = makeFile("notes.exe", { type: "application/octet-stream" });
      uploadDocument(textFile);

      expect(alertMock).toHaveBeenCalledWith(
        "صيغة الملف غير مدعومة. الصيغ المسموح بها: PDF, JPG, PNG",
      );
      expect(screen.queryByText("notes.exe")).not.toBeInTheDocument();
    });

    it("rejects an oversized file with an alert", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const hugeFile = makeFile("huge.pdf", {
        type: "application/pdf",
        size: MAX_UPLOAD_SIZE_BYTES + 1,
      });
      await user.upload(getDocumentFileInput(), hugeFile);

      expect(alertMock).toHaveBeenCalledWith(
        "حجم الملف يجب ألا يتجاوز 10 ميجابايت",
      );
      expect(screen.queryByText("huge.pdf")).not.toBeInTheDocument();
    });

    it("removes an uploaded file from the list", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const pdf = makeFile("remove-me.pdf");
      await user.upload(getDocumentFileInput(), pdf);
      const fileRow = screen.getByText("remove-me.pdf").closest(
        ".flex.items-center",
      ) as HTMLElement;
      await user.click(within(fileRow).getByRole("button"));

      expect(screen.queryByText("remove-me.pdf")).not.toBeInTheDocument();
    });

    it("accepts a file dropped onto the upload area", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const dropzone = screen.getByRole("button", { name: /ارفق المستندات/i });
      const dropped = makeFile("dropped.pdf");

      fireEvent.drop(dropzone, {
        dataTransfer: { files: [dropped] },
      });

      expect(await screen.findByText("dropped.pdf")).toBeInTheDocument();
    });

    it("blocks submission when documents are missing", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      await user.type(getNationalIdInput(), "1234567890");
      await user.type(getIbanInput(), VALID_IBAN);
      await acceptTerms(user);
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("الرجاء إرفاق المستندات المطلوبة"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });

    it("shows national id validation errors when the field is empty", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const pdf = makeFile("contract.pdf", { type: "application/pdf" });
      await user.upload(getDocumentFileInput(), pdf);
      await user.type(getIbanInput(), VALID_IBAN);
      await acceptTerms(user);
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("الرجاء إدخال رقم الهوية الوطنية"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });

    it("shows IBAN validation errors when the field is empty", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      const pdf = makeFile("contract.pdf", { type: "application/pdf" });
      await user.upload(getDocumentFileInput(), pdf);
      await user.type(getNationalIdInput(), "1234567890");
      await acceptTerms(user);
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("الرجاء إدخال رقم آيبان صحيح"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });

    it("blocks submission when terms are not accepted", async () => {
      const user = userEvent.setup();
      render(<SignupForm />);
      await advanceToStepTwo(user);

      await user.type(getNationalIdInput(), "1234567890");
      await user.type(getIbanInput(), VALID_IBAN);
      await user.upload(getDocumentFileInput(), makeFile("doc.pdf"));
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("يجب الموافقة على الشروط والأحكام للمتابعة"),
      ).toBeInTheDocument();
      expect(apiFetch).not.toHaveBeenCalled();
    });
  });

  describe("legal content modals", () => {
    it("opens the terms modal and renders fetched content with line breaks", async () => {
      const user = userEvent.setup();
      mockLegalPageSuccess("شروط الاستخدام\n\nالفقرة الثانية");
      render(<SignupForm />);
      await advanceToStepTwo(user);

      await user.click(
        screen.getByRole("button", { name: "الشروط والأحكام" }),
      );

      expect(await screen.findByText(/شروط الاستخدام/)).toBeInTheDocument();
      expect(screen.getByText(/الفقرة الثانية/)).toBeInTheDocument();
      expect(screen.getByRole("heading", { name: "الشروط والأحكام" })).toBeInTheDocument();
      expect(apiFetch).toHaveBeenCalledWith("/terms", {
        method: "GET",
        successCase: expect.any(Function),
        errorCase: expect.any(Function),
      });
    });

    it("opens the privacy modal and fetches privacy content", async () => {
      const user = userEvent.setup();
      mockLegalPageSuccess("نص سياسة الخصوصية");
      render(<SignupForm />);
      await advanceToStepTwo(user);

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

      expect(await screen.findByText("نص سياسة الخصوصية")).toBeInTheDocument();
      expect(screen.getByRole("heading", { name: "سياسة الخصوصية" })).toBeInTheDocument();
      expect(apiFetch).toHaveBeenCalledWith("/privacy", {
        method: "GET",
        successCase: expect.any(Function),
        errorCase: expect.any(Function),
      });
    });

    it("does not toggle the terms checkbox when opening a legal modal", async () => {
      const user = userEvent.setup();
      mockLegalPageSuccess("محتوى الشروط");
      render(<SignupForm />);
      await advanceToStepTwo(user);

      await user.click(
        screen.getByRole("button", { name: "الشروط والأحكام" }),
      );

      await screen.findByText("محتوى الشروط");
      expect(getTermsCheckbox()).not.toHaveClass("bg-[#FCD704]/20");
    });
  });

  describe("registration submission", () => {
    it("submits FormData to register and shows the success screen", async () => {
      const user = userEvent.setup();
      mockApiSuccess();
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await fillStepTwo(user, makeFile("national-id.pdf"));
      await user.click(getCreateAccountButton());

      await waitFor(() => {
        expect(apiFetch).toHaveBeenCalledTimes(1);
      });

      const [, options] = vi.mocked(apiFetch).mock.calls[0];
      expect(options).toMatchObject({
        method: "POST",
        getSuccess: true,
      });

      const body = options?.body as FormData;
      expect(body.get("first_name")).toBe("أحمد");
      expect(body.get("last_name")).toBe("المستثمر");
      expect(body.get("email")).toBe(VALID_EMAIL);
      expect(body.get("phone")).toBe(VALID_PHONE);
      expect(body.get("country_code")).toBe("966");
      expect(body.get("password")).toBe(VALID_PASSWORD);
      expect(body.get("password_confirmation")).toBe(VALID_PASSWORD);
      expect(body.get("national_id")).toBe("1234567890");
      expect(body.get("iban")).toBe(VALID_IBAN);
      expect(body.get("vehicle_count")).toBe("1-2");
      expect(body.get("terms_accepted")).toBe("1");
      expect(body.getAll("documents[]")).toHaveLength(1);

      expect(setAuth).toHaveBeenCalledWith(
        expect.objectContaining({ token: "signup-token" }),
      );
      expect(await screen.findByText("تم إنشاء الحساب!")).toBeInTheDocument();
      expect(screen.getByRole("link", { name: "تسجيل الدخول" })).toHaveAttribute(
        "href",
        "/login",
      );
    });

    it("shows a loading state and disables submit during registration", async () => {
      const user = userEvent.setup();
      let resolveFetch!: (envelope: ApiEnvelope<AuthInfo>) => void;

      vi.mocked(apiFetch).mockImplementation(
        (_url, options) =>
          new Promise<ApiEnvelope<AuthInfo>>((resolve) => {
            resolveFetch = (envelope) => {
              options?.successCase?.(envelope);
              resolve(envelope);
            };
          }),
      );

      render(<SignupForm />);
      await advanceToStepTwo(user);
      await fillStepTwo(user);
      await user.click(getCreateAccountButton());

      expect(
        screen.getByRole("button", { name: /جاري المعالجة/ }),
      ).toBeDisabled();

      resolveFetch({
        key: "success",
        msg: "",
        data: { token: "signup-token" } as AuthInfo,
      });

      await screen.findByText("تم إنشاء الحساب!");
    });

    it("shows an API error and remains on step two", async () => {
      const user = userEvent.setup();
      mockApiFailure("البريد الإلكتروني مستخدم مسبقاً");
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await fillStepTwo(user);
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("البريد الإلكتروني مستخدم مسبقاً"),
      ).toBeInTheDocument();
      expect(screen.queryByText("تم إنشاء الحساب!")).not.toBeInTheDocument();
      expect(getCreateAccountButton()).not.toBeDisabled();
    });

    it("falls back to a default registration error when the API has no msg", async () => {
      const user = userEvent.setup();
      vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
        const envelope: ApiEnvelope = { key: "fail", msg: "" };
        options?.errorCase?.(envelope);
        return envelope;
      });
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await fillStepTwo(user);
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("حدث خطأ أثناء إنشاء الحساب، حاول مرة أخرى"),
      ).toBeInTheDocument();
    });

    it("shows a verification error when the API returns needVerify", async () => {
      const user = userEvent.setup();
      vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
        const envelope: ApiEnvelope = {
          key: "needVerify",
          msg: "يرجى تأكيد البريد الإلكتروني",
        };
        options?.needVerifyCase?.(envelope);
        return envelope;
      });
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await fillStepTwo(user);
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("يرجى تأكيد البريد الإلكتروني"),
      ).toBeInTheDocument();
      expect(push).not.toHaveBeenCalled();
    });

    it("navigates to login when registration requires approval", async () => {
      const user = userEvent.setup();
      vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
        const envelope: ApiEnvelope = { key: "needApprove", msg: "" };
        options?.needApproveCase?.(envelope);
        return envelope;
      });
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await fillStepTwo(user);
      await user.click(getCreateAccountButton());

      await waitFor(() => {
        expect(push).toHaveBeenCalledWith("/login");
      });
    });

    it("advances to the success step without persisting auth when success has no data", async () => {
      const user = userEvent.setup();
      vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
        const envelope: ApiEnvelope = { key: "success", msg: "" };
        options?.successCase?.(envelope);
        return envelope;
      });
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await fillStepTwo(user);
      await user.click(getCreateAccountButton());

      expect(await screen.findByText("تم إنشاء الحساب!")).toBeInTheDocument();
      expect(setAuth).not.toHaveBeenCalled();
    });

    it("falls back to a default verification error when needVerify has no msg", async () => {
      const user = userEvent.setup();
      vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
        const envelope: ApiEnvelope = { key: "needVerify", msg: "" };
        options?.needVerifyCase?.(envelope);
        return envelope;
      });
      render(<SignupForm />);

      await advanceToStepTwo(user);
      await fillStepTwo(user);
      await user.click(getCreateAccountButton());

      expect(
        await screen.findByText("يرجى التحقق من حسابك"),
      ).toBeInTheDocument();
    });
  });
});
