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 ChangePasswordForm from "@/components/profile/ChangePasswordForm";
import type { ApiEnvelope } from "@/lib/api/types";

const setAuth = vi.fn();

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

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

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

const VALID_NEW_PASSWORD = "Abcd1234!";

function getCurrentPasswordInput() {
  return screen.getByPlaceholderText("أدخل كلمة المرور الحالية");
}

function getNewPasswordInput() {
  return screen.getByPlaceholderText("أدخل كلمة مرور جديدة");
}

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

function getSubmitButton() {
  return screen.getByRole("button", { name: "تغيير كلمة المرور" });
}

function getPasswordToggle(input: HTMLElement) {
  const field = input.closest(".bg-\\[\\#13151A\\]") as HTMLElement;
  return field.querySelector('button[type="button"]') as HTMLButtonElement;
}

async function submitForm() {
  const form = getSubmitButton().closest("form");
  if (!form) {
    throw new Error("Form not found");
  }
  fireEvent.submit(form);
}

async function fillValidPasswordForm(
  user: ReturnType<typeof userEvent.setup>,
  currentPassword = "OldPass1!",
) {
  await user.type(getCurrentPasswordInput(), currentPassword);
  await user.type(getNewPasswordInput(), VALID_NEW_PASSWORD);
  await user.type(getConfirmPasswordInput(), VALID_NEW_PASSWORD);
}

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

  it("renders the password fields and submit action", () => {
    render(<ChangePasswordForm />);

    expect(getCurrentPasswordInput()).toBeInTheDocument();
    expect(getNewPasswordInput()).toBeInTheDocument();
    expect(getConfirmPasswordInput()).toBeInTheDocument();
    expect(getSubmitButton()).toBeInTheDocument();
  });

  it("submits valid passwords and shows the success state", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope<{ token: string }> = {
        key: "success",
        data: { token: "fresh-auth-token" },
        msg: "",
      };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<ChangePasswordForm />);
    await fillValidPasswordForm(user);
    await user.click(getSubmitButton());

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

    expect(apiFetch).toHaveBeenCalledWith("/provider/update-password", {
      method: "PATCH",
      body: JSON.stringify({
        old_password: "OldPass1!",
        password: VALID_NEW_PASSWORD,
      }),
      successCase: expect.any(Function),
      errorCase: expect.any(Function),
    });

    expect(await screen.findByText("تم تغيير كلمة المرور")).toBeInTheDocument();
    expect(screen.getByText("العودة للملف الشخصي")).toBeInTheDocument();
    expect(
      screen.queryByRole("button", { name: "تغيير كلمة المرور" }),
    ).not.toBeInTheDocument();
  });

  it("does not persist auth state when the API returns a token", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope<{ token: string }> = {
        key: "success",
        data: { token: "fresh-auth-token" },
        msg: "",
      };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(<ChangePasswordForm />);
    await fillValidPasswordForm(user);
    await user.click(getSubmitButton());

    await screen.findByText("تم تغيير كلمة المرور");

    expect(setAuth).not.toHaveBeenCalled();
  });

  it("shows an API error and does not show the success state", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope = {
        key: "fail",
        msg: "كلمة المرور الحالية غير صحيحة",
      };
      options?.errorCase?.(envelope);
      return envelope;
    });

    render(<ChangePasswordForm />);
    await fillValidPasswordForm(user);
    await user.click(getSubmitButton());

    expect(
      await screen.findByText("كلمة المرور الحالية غير صحيحة"),
    ).toBeInTheDocument();
    expect(screen.queryByText("تم تغيير كلمة المرور")).not.toBeInTheDocument();
    expect(getSubmitButton()).not.toBeDisabled();
  });

  it("falls back to a default API error when the response 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(<ChangePasswordForm />);
    await fillValidPasswordForm(user);
    await user.click(getSubmitButton());

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

  it("shows unmet password requirements before a new password is entered", () => {
    render(<ChangePasswordForm />);

    expect(screen.getByText("8 أحرف على الأقل").className).toContain(
      "text-gray-500",
    );
    expect(
      document.querySelectorAll(".ri-close-line.text-gray-600").length,
    ).toBeGreaterThan(0);
  });

  it("blocks submission when confirmation does not match", async () => {
    const user = userEvent.setup();

    render(<ChangePasswordForm />);

    await user.type(getCurrentPasswordInput(), "OldPass1!");
    await user.type(getNewPasswordInput(), VALID_NEW_PASSWORD);
    await user.type(getConfirmPasswordInput(), "Mismatch1!");

    expect(getSubmitButton()).toBeDisabled();
    expect(apiFetch).not.toHaveBeenCalled();
  });

  it("shows an error when the current password is empty on submit", async () => {
    const user = userEvent.setup();

    render(<ChangePasswordForm />);
    await user.type(getNewPasswordInput(), VALID_NEW_PASSWORD);
    await user.type(getConfirmPasswordInput(), VALID_NEW_PASSWORD);
    await submitForm();

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

  it("shows an error when the new password does not meet all rules", async () => {
    const user = userEvent.setup();

    render(<ChangePasswordForm />);
    await user.type(getCurrentPasswordInput(), "OldPass1!");
    await user.type(getNewPasswordInput(), "weak");
    await user.type(getConfirmPasswordInput(), "weak");
    await submitForm();

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

  it("shows a mismatch error when confirmation differs after bypassing the disabled button", async () => {
    const user = userEvent.setup();

    render(<ChangePasswordForm />);
    await user.type(getCurrentPasswordInput(), "OldPass1!");
    await user.type(getNewPasswordInput(), VALID_NEW_PASSWORD);
    await user.type(getConfirmPasswordInput(), "Mismatch1!");
    await submitForm();

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

  it.each([
    ["Ab1!", /ضعيفة/],
    ["Abcd123!", /مقبولة/],
    ["abcdefghij", /جيدة/],
    ["Abcd1234!Extra", /قوية جداً/],
  ] as const)(
    "shows password strength feedback for %s",
    async (password, labelPattern) => {
      const user = userEvent.setup();

      render(<ChangePasswordForm />);
      await user.type(getNewPasswordInput(), password);

      expect(screen.getByText(labelPattern)).toBeInTheDocument();
    },
  );

  it("toggles visibility for current, new, and confirm password fields", async () => {
    const user = userEvent.setup();

    render(<ChangePasswordForm />);

    const currentToggle = getPasswordToggle(getCurrentPasswordInput());
    const newToggle = getPasswordToggle(getNewPasswordInput());
    const confirmToggle = getPasswordToggle(getConfirmPasswordInput());

    expect(getCurrentPasswordInput()).toHaveAttribute("type", "password");
    await user.click(currentToggle);
    expect(getCurrentPasswordInput()).toHaveAttribute("type", "text");

    expect(getNewPasswordInput()).toHaveAttribute("type", "password");
    await user.click(newToggle);
    expect(getNewPasswordInput()).toHaveAttribute("type", "text");

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