import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import ForgotPasswordPage from "@/app/forgot-password/page";
import type { ApiEnvelope } from "@/lib/api/types";
import { RESEND_COOLDOWN_SECONDS } from "@/hooks/useResendCountdown";

const replace = vi.fn();

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

vi.mock("@/lib/auth/store", () => ({
  useAuthStore: (
    selector: (state: { authInfo: null; _hydrated: boolean }) => unknown,
  ) => selector({ authInfo: null, _hydrated: true }),
}));

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

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

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

const VALID_EMAIL = "user@example.com";
const VALID_CODE = "123456";
const VALID_PASSWORD = "Abcd1234!";

const alertMock = vi.fn();

function getEmailInput() {
  return screen.getByPlaceholderText("أدخل بريدك الإلكتروني");
}

function getCodeInput() {
  return screen.getByPlaceholderText("123456");
}

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

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

function mockApiSuccess() {
  vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
    const envelope: ApiEnvelope = { key: "success", msg: "" };
    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 submitEmailStep(user: ReturnType<typeof userEvent.setup>) {
  await user.clear(getEmailInput());
  await user.type(getEmailInput(), VALID_EMAIL);
  await user.click(screen.getByRole("button", { name: "إرسال رمز التحقق" }));
}

async function advanceToCodeStep(user: ReturnType<typeof userEvent.setup>) {
  mockApiSuccess();
  await submitEmailStep(user);
  await waitFor(() => {
    expect(screen.getByPlaceholderText("123456")).toBeInTheDocument();
  });
}

async function advanceToCodeStepWithFakeTimers() {
  mockApiSuccess();
  fireEvent.change(getEmailInput(), { target: { value: VALID_EMAIL } });
  fireEvent.click(screen.getByRole("button", { name: "إرسال رمز التحقق" }));

  await act(async () => {
    await Promise.resolve();
  });

  expect(screen.getByPlaceholderText("123456")).toBeInTheDocument();
  expect(screen.getByText(/إعادة الإرسال خلال/)).toBeInTheDocument();
}

function finishResendCooldown() {
  act(() => {
    vi.advanceTimersByTime(RESEND_COOLDOWN_SECONDS * 1000);
  });
}

async function advanceToPasswordStep(user: ReturnType<typeof userEvent.setup>) {
  await advanceToCodeStep(user);
  mockApiSuccess();
  await user.type(getCodeInput(), VALID_CODE);
  await user.click(screen.getByRole("button", { name: "التحقق والمتابعة" }));
  await waitFor(() => {
    expect(getPasswordInput()).toBeInTheDocument();
  });
}

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

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

  describe("initial state — step 1 (email)", () => {
    it("renders the forgot-password heading, email field, and first-step submit action", () => {
      render(<ForgotPasswordPage />);

      expect(screen.getByText("استعادة كلمة المرور")).toBeInTheDocument();
      expect(
        screen.getByText("اتبع الخطوات لاستعادة حسابك بأمان"),
      ).toBeInTheDocument();
      expect(screen.getByLabelText("البريد الإلكتروني")).toBeInTheDocument();
      expect(getEmailInput()).toBeInTheDocument();
      expect(
        screen.getByRole("button", { name: "إرسال رمز التحقق" }),
      ).toBeInTheDocument();
      expect(
        screen.queryByRole("button", { name: "→ العودة للخطوة السابقة" }),
      ).not.toBeInTheDocument();
      expect(screen.getByRole("link", { name: "العودة لتسجيل الدخول" })).toHaveAttribute(
        "href",
        "/login",
      );
    });

    it("submits the email step through the form submit handler", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      mockApiSuccess();

      await user.type(getEmailInput(), VALID_EMAIL);
      fireEvent.submit(getEmailInput().closest("form")!);

      await waitFor(() => {
        expect(apiFetch).toHaveBeenCalledWith("/provider/password/forget", {
          method: "POST",
          body: JSON.stringify({ email: VALID_EMAIL }),
          successCase: expect.any(Function),
          errorCase: expect.any(Function),
        });
      });
    });

    it("rejects an empty email without calling the API", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);

      await user.click(screen.getByRole("button", { name: "إرسال رمز التحقق" }));

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

    it("rejects a malformed email without calling the API", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);

      await user.type(getEmailInput(), "abc");
      fireEvent.submit(getEmailInput().closest("form")!);

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

    it("submits a valid email to the forget endpoint and advances to the code step", async () => {
      const user = userEvent.setup();
      mockApiSuccess();
      render(<ForgotPasswordPage />);

      await submitEmailStep(user);

      await waitFor(() => {
        expect(apiFetch).toHaveBeenCalledWith("/provider/password/forget", {
          method: "POST",
          body: JSON.stringify({ email: VALID_EMAIL }),
          successCase: expect.any(Function),
          errorCase: expect.any(Function),
        });
      });

      expect(getCodeInput()).toBeInTheDocument();
      expect(
        screen.getByRole("button", { name: "التحقق والمتابعة" }),
      ).toBeInTheDocument();
      expect(
        screen.getByRole("button", { name: "→ العودة للخطوة السابقة" }),
      ).toBeInTheDocument();
    });

    it("shows a loading state while the forget request is in flight", async () => {
      const user = userEvent.setup();
      let resolveFetch!: (envelope: ApiEnvelope) => void;

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

      render(<ForgotPasswordPage />);
      await user.type(getEmailInput(), VALID_EMAIL);
      await user.click(screen.getByRole("button", { name: "إرسال رمز التحقق" }));

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

      resolveFetch({ key: "success", msg: "" });

      await waitFor(() => {
        expect(getCodeInput()).toBeInTheDocument();
      });
    });

    it("stays on the email step and shows an API error when forget fails", async () => {
      const user = userEvent.setup();
      mockApiFailure("البريد غير مسجل");
      render(<ForgotPasswordPage />);

      await submitEmailStep(user);

      expect(await screen.findByText("البريد غير مسجل")).toBeInTheDocument();
      expect(getEmailInput()).toBeInTheDocument();
      expect(screen.queryByPlaceholderText("123456")).not.toBeInTheDocument();
    });

    it("falls back to a default error message when the API error 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(<ForgotPasswordPage />);

      await submitEmailStep(user);

      expect(
        await screen.findByText("تعذّر إرسال الكود، تأكد من البريد الإلكتروني"),
      ).toBeInTheDocument();
    });
  });

  describe("step 2 (OTP verification)", () => {
    it("rejects an invalid OTP without calling the API", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToCodeStep(user);

      await user.type(getCodeInput(), "12");
      await user.click(screen.getByRole("button", { name: "التحقق والمتابعة" }));

      expect(
        await screen.findByText("الكود يجب أن يكون 6 أرقام"),
      ).toBeInTheDocument();
      expect(apiFetch).toHaveBeenCalledTimes(1);
    });

    it("submits email and code to check-code and advances to the password step", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToCodeStep(user);
      mockApiSuccess();

      await user.type(getCodeInput(), VALID_CODE);
      await user.click(screen.getByRole("button", { name: "التحقق والمتابعة" }));

      await waitFor(() => {
        expect(apiFetch).toHaveBeenLastCalledWith("/provider/password/check-code", {
          method: "POST",
          body: JSON.stringify({ email: VALID_EMAIL, code: VALID_CODE }),
          successCase: expect.any(Function),
          errorCase: expect.any(Function),
        });
      });

      expect(getPasswordInput()).toBeInTheDocument();
      expect(getConfirmPasswordInput()).toBeInTheDocument();
      expect(screen.getByText("متطلبات كلمة المرور:")).toBeInTheDocument();
    });

    it("shows an API error and remains on the code step when verification fails", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToCodeStep(user);
      mockApiFailure("الكود غير صحيح");

      await user.type(getCodeInput(), VALID_CODE);
      await user.click(screen.getByRole("button", { name: "التحقق والمتابعة" }));

      expect(await screen.findByText("الكود غير صحيح")).toBeInTheDocument();
      expect(getCodeInput()).toBeInTheDocument();
    });

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

      await user.type(getCodeInput(), VALID_CODE);
      await user.click(screen.getByRole("button", { name: "التحقق والمتابعة" }));

      expect(
        await screen.findByText("الكود غير صحيح أو منتهي الصلاحية"),
      ).toBeInTheDocument();
    });

    it("shows a resend countdown after the verification code is sent", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToCodeStep(user);

      expect(screen.getByText(/إعادة الإرسال خلال/)).toBeInTheDocument();
      expect(screen.getByText(String(RESEND_COOLDOWN_SECONDS))).toBeInTheDocument();
      expect(
        screen.queryByRole("button", { name: "إعادة إرسال الكود" }),
      ).not.toBeInTheDocument();
    });

    it("resends the code and shows a success alert on resend success", async () => {
      vi.useFakeTimers();
      render(<ForgotPasswordPage />);
      await advanceToCodeStepWithFakeTimers();
      finishResendCooldown();
      mockApiSuccess();

      fireEvent.click(screen.getByRole("button", { name: "إعادة إرسال الكود" }));

      await act(async () => {
        await Promise.resolve();
      });

      expect(apiFetch).toHaveBeenLastCalledWith("/provider/password/forget", {
        method: "POST",
        body: JSON.stringify({ email: VALID_EMAIL }),
        successCase: expect.any(Function),
        errorCase: expect.any(Function),
      });
      expect(alertMock).toHaveBeenCalledWith(
        "تم إعادة إرسال الكود إلى بريدك الإلكتروني",
      );
      expect(screen.getByText(/إعادة الإرسال خلال/)).toBeInTheDocument();
      expect(screen.getByText(String(RESEND_COOLDOWN_SECONDS))).toBeInTheDocument();
    });

    it("restarts the countdown after a successful resend", async () => {
      vi.useFakeTimers();
      render(<ForgotPasswordPage />);
      await advanceToCodeStepWithFakeTimers();
      finishResendCooldown();
      mockApiSuccess();
      fireEvent.click(screen.getByRole("button", { name: "إعادة إرسال الكود" }));

      await act(async () => {
        await Promise.resolve();
      });

      expect(screen.getByText(String(RESEND_COOLDOWN_SECONDS))).toBeInTheDocument();

      act(() => {
        vi.advanceTimersByTime(10_000);
      });

      expect(screen.getByText(String(RESEND_COOLDOWN_SECONDS - 10))).toBeInTheDocument();
    });

    it("shows an error when resend fails", async () => {
      vi.useFakeTimers();
      render(<ForgotPasswordPage />);
      await advanceToCodeStepWithFakeTimers();
      finishResendCooldown();
      mockApiFailure("تعذّر الإرسال");

      fireEvent.click(screen.getByRole("button", { name: "إعادة إرسال الكود" }));

      await act(async () => {
        await Promise.resolve();
      });

      expect(screen.getByText("تعذّر الإرسال")).toBeInTheDocument();
    });

    it("falls back to a default resend error when the API error has no msg", async () => {
      vi.useFakeTimers();
      render(<ForgotPasswordPage />);
      await advanceToCodeStepWithFakeTimers();
      finishResendCooldown();
      vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
        const envelope: ApiEnvelope = { key: "fail" };
        options?.errorCase?.(envelope);
        return envelope;
      });

      fireEvent.click(screen.getByRole("button", { name: "إعادة إرسال الكود" }));

      await act(async () => {
        await Promise.resolve();
      });

      expect(screen.getByText("تعذّر إعادة إرسال الكود")).toBeInTheDocument();
    });

    it("navigates back to the email step from the code step", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToCodeStep(user);

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

      expect(getEmailInput()).toBeInTheDocument();
      expect(screen.queryByPlaceholderText("123456")).not.toBeInTheDocument();
    });
  });

  describe("step 3 (new password)", () => {
    it("updates password requirements as the user types", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

      await user.type(getPasswordInput(), "Ab1!");

      expect(screen.getByText("8 أحرف على الأقل")).toBeInTheDocument();
      expect(screen.getByText("حرف كبير واحد على الأقل")).toBeInTheDocument();
      expect(screen.getByText("رقم واحد على الأقل")).toBeInTheDocument();
      expect(screen.getByText("رمز خاص (!@#$...)")).toBeInTheDocument();
      expect(
        screen.getByRole("button", { name: "تغيير كلمة المرور" }),
      ).toBeDisabled();
    });

    it("shows password strength feedback when a password is entered", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

      await user.type(getPasswordInput(), "Ab1!");

      expect(screen.getByText(/قوة كلمة المرور:/)).toBeInTheDocument();
      expect(screen.getByText(/ضعيفة/)).toBeInTheDocument();
    });

    it("shows the medium-strength label with yellow styling for six-character passwords", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

      await user.type(getPasswordInput(), "Abcd12");

      expect(screen.getByText(/مقبولة/)).toBeInTheDocument();
      expect(screen.getByText(/قوة كلمة المرور:/).className).toContain(
        "text-yellow-400",
      );
    });

    it("shows password validation errors when the form is submitted with an invalid password", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

      await user.type(getPasswordInput(), "short");
      fireEvent.submit(getPasswordInput().closest("form")!);

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

    it("shows stronger strength labels for longer compliant passwords", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

      await user.type(getPasswordInput(), "Abcd1234!Extra");

      expect(screen.getByText(/قوية جداً/)).toBeInTheDocument();
    });

    it("shows the good strength label for long passwords missing mixed character rules", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

      await user.type(getPasswordInput(), "abcdefghij");

      expect(screen.getByText(/جيدة/)).toBeInTheDocument();
      expect(screen.getByText(/قوة كلمة المرور:/).className).toContain(
        "text-blue-400",
      );
    });

    it("toggles password visibility for the new-password and confirmation fields", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

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

      const passwordToggle = within(passwordField).getByRole("button");
      const confirmToggle = within(confirmField).getByRole("button");

      expect(getPasswordInput()).toHaveAttribute("type", "password");
      await user.click(passwordToggle);
      expect(getPasswordInput()).toHaveAttribute("type", "text");

      expect(getConfirmPasswordInput()).toHaveAttribute("type", "password");
      await user.click(confirmToggle);
      expect(getConfirmPasswordInput()).toHaveAttribute("type", "text");
    });

    it("rejects a mismatched confirmation password", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

      await user.type(getPasswordInput(), VALID_PASSWORD);
      await user.type(getConfirmPasswordInput(), "Mismatch1!");
      await user.click(screen.getByRole("button", { name: "تغيير كلمة المرور" }));

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

    it("submits the reset request through the form handler and shows the success screen", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);
      mockApiSuccess();

      await user.type(getPasswordInput(), VALID_PASSWORD);
      await user.type(getConfirmPasswordInput(), VALID_PASSWORD);
      fireEvent.submit(getPasswordInput().closest("form")!);

      await waitFor(() => {
        expect(apiFetch).toHaveBeenLastCalledWith("/provider/password/reset", {
          method: "POST",
          body: JSON.stringify({
            email: VALID_EMAIL,
            code: VALID_CODE,
            password: VALID_PASSWORD,
            password_confirmation: VALID_PASSWORD,
          }),
          successCase: expect.any(Function),
          errorCase: expect.any(Function),
        });
      });

      expect(await screen.findByText("تم استعادة الحساب!")).toBeInTheDocument();
    });

    it("submits the reset request and shows the success screen", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);
      mockApiSuccess();

      await user.type(getPasswordInput(), VALID_PASSWORD);
      await user.type(getConfirmPasswordInput(), VALID_PASSWORD);
      await user.click(screen.getByRole("button", { name: "تغيير كلمة المرور" }));

      await waitFor(() => {
        expect(apiFetch).toHaveBeenLastCalledWith("/provider/password/reset", {
          method: "POST",
          body: JSON.stringify({
            email: VALID_EMAIL,
            code: VALID_CODE,
            password: VALID_PASSWORD,
            password_confirmation: VALID_PASSWORD,
          }),
          successCase: expect.any(Function),
          errorCase: expect.any(Function),
        });
      });

      expect(await screen.findByText("تم استعادة الحساب!")).toBeInTheDocument();
      expect(
        screen.getByText("تم تعيين كلمة المرور الجديدة بنجاح، يمكنك الآن تسجيل الدخول."),
      ).toBeInTheDocument();
      expect(screen.getByRole("link", { name: "تسجيل الدخول" })).toHaveAttribute(
        "href",
        "/login",
      );
    });

    it("shows an API error and remains on the password step when reset fails", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);
      mockApiFailure("تعذّر التعيين");

      await user.type(getPasswordInput(), VALID_PASSWORD);
      await user.type(getConfirmPasswordInput(), VALID_PASSWORD);
      await user.click(screen.getByRole("button", { name: "تغيير كلمة المرور" }));

      expect(await screen.findByText("تعذّر التعيين")).toBeInTheDocument();
      expect(getPasswordInput()).toBeInTheDocument();
    });

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

      await user.type(getPasswordInput(), VALID_PASSWORD);
      await user.type(getConfirmPasswordInput(), VALID_PASSWORD);
      await user.click(screen.getByRole("button", { name: "تغيير كلمة المرور" }));

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

    it("navigates back to the code step from the password step", async () => {
      const user = userEvent.setup();
      render(<ForgotPasswordPage />);
      await advanceToPasswordStep(user);

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

      expect(getCodeInput()).toBeInTheDocument();
      expect(screen.queryByPlaceholderText("8 أحرف على الأقل")).not.toBeInTheDocument();
    });
  });
});
