import { beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import VerifyCodeForm from "@/components/auth/VerifyCodeForm";
import { FCM_DEVICE_ID_UNAVAILABLE_MSG } from "@/lib/firebase/messaging";
import type { ApiEnvelope } from "@/lib/api/types";

const TEST_FCM_TOKEN = "TEST_FCM_TOKEN_123";

const replace = vi.fn();
const setAuth = vi.fn();
const setPendingLogin = vi.fn();
const clearPendingLogin = vi.fn();
const { resolveAuthDeviceId, markFcmTokenSynced, mockAuthState } = vi.hoisted(() => ({
  resolveAuthDeviceId: vi.fn(),
  markFcmTokenSynced: vi.fn(),
  mockAuthState: {
    pendingLogin: {
      email: "user@example.com",
      password: "Abcd1234!",
      device_id: "TEST_FCM_TOKEN_123",
    } as
      | { email: string; password: string; device_id?: string }
      | null,
    _hydrated: true,
  },
}));

vi.mock("@/lib/firebase/authDeviceId", () => ({
  resolveAuthDeviceId,
}));

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

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

vi.mock("@/lib/firebase/messaging", async (importOriginal) => {
  const actual =
    await importOriginal<typeof import("@/lib/firebase/messaging")>();
  return {
    ...actual,
    markFcmTokenSynced,
  };
});

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

const { refreshUnreadNotificationCount } = vi.hoisted(() => ({
  refreshUnreadNotificationCount: vi.fn(),
}));

vi.mock("@/lib/notifications/count", () => ({
  refreshUnreadNotificationCount,
}));

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

describe("VerifyCodeForm device_id", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockAuthState.pendingLogin = {
      email: "user@example.com",
      password: "Abcd1234!",
      device_id: TEST_FCM_TOKEN,
    };
    mockAuthState._hydrated = true;
    resolveAuthDeviceId.mockImplementation(async ({ cachedDeviceId }) => ({
      token: cachedDeviceId ?? TEST_FCM_TOKEN,
      reason: null,
    }));
  });

  it("sends the exact FCM token as device_id on verify-login", async () => {
    const user = userEvent.setup();
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const body = JSON.parse(String(options?.body));
      expect(body.device_id).toBe(TEST_FCM_TOKEN);
      expect(body.device_id).not.toBe("");
      options?.successCase?.({
        key: "success",
        data: { token: "auth-token" },
      });
      return { key: "success", data: { token: "auth-token" } };
    });

    render(<VerifyCodeForm />);

    await user.type(screen.getByPlaceholderText("123456"), "123456");
    await user.click(screen.getByRole("button", { name: "تأكيد الرمز" }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/verify-login",
        expect.objectContaining({ method: "POST" }),
      );
    });
    expect(markFcmTokenSynced).toHaveBeenCalledWith(TEST_FCM_TOKEN);
    expect(refreshUnreadNotificationCount).toHaveBeenCalledTimes(1);
  });

  it("sends the exact FCM token as device_id when resending the code", async () => {
    const user = userEvent.setup();
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const body = JSON.parse(String(options?.body));
      expect(body.device_id).toBe(TEST_FCM_TOKEN);
      options?.needVerifyCase?.({ key: "needVerify" });
      return { key: "needVerify" };
    });

    render(<VerifyCodeForm />);

    await user.click(screen.getByRole("button", { name: "إعادة إرسال الرمز" }));

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith(
        "/provider/login",
        expect.objectContaining({ method: "POST" }),
      );
    });
  });

  it("does not submit when FCM token retrieval fails", async () => {
    const user = userEvent.setup();
    let callCount = 0;
    resolveAuthDeviceId.mockImplementation(async ({ cachedDeviceId }) => {
      callCount += 1;
      if (callCount >= 2) {
        return { token: null, reason: "permission_dismissed" };
      }
      return { token: cachedDeviceId ?? TEST_FCM_TOKEN, reason: null };
    });

    render(<VerifyCodeForm />);

    await user.type(screen.getByPlaceholderText("123456"), "123456");
    await user.click(screen.getByRole("button", { name: "تأكيد الرمز" }));

    await waitFor(() => {
      expect(screen.getByText(FCM_DEVICE_ID_UNAVAILABLE_MSG)).toBeInTheDocument();
    });
    expect(apiFetch).not.toHaveBeenCalled();
  });

  it("shows the verification loading label while the request is pending", async () => {
    const user = userEvent.setup();
    vi.mocked(apiFetch).mockImplementation(() => new Promise(() => {}));

    render(<VerifyCodeForm />);
    await user.type(screen.getByPlaceholderText("123456"), "123456");
    await user.click(screen.getByRole("button", { name: "تأكيد الرمز" }));

    expect(await screen.findByText("جاري التحقق...")).toBeInTheDocument();
  });

  it("shows the resend loading label while the request is pending", async () => {
    const user = userEvent.setup();
    vi.mocked(apiFetch).mockImplementation(() => new Promise(() => {}));

    render(<VerifyCodeForm />);
    await user.click(screen.getByRole("button", { name: "إعادة إرسال الرمز" }));

    expect(await screen.findByText("جاري الإرسال...")).toBeInTheDocument();
  });

  it("redirects to login after hydration when there is no pending login", async () => {
    mockAuthState.pendingLogin = null;

    render(<VerifyCodeForm />);

    await waitFor(() => {
      expect(replace).toHaveBeenCalledWith("/login");
    });
    expect(
      screen.queryByText("التحقق من الحساب 🔐"),
    ).not.toBeInTheDocument();
  });

  it("shows FCM unavailable error when resend cannot obtain a token", async () => {
    const user = userEvent.setup();
    let callCount = 0;
    resolveAuthDeviceId.mockImplementation(async ({ cachedDeviceId }) => {
      callCount += 1;
      if (callCount >= 2) {
        return { token: null, reason: "permission_dismissed" };
      }
      return { token: cachedDeviceId ?? TEST_FCM_TOKEN, reason: null };
    });

    render(<VerifyCodeForm />);

    await user.click(screen.getByRole("button", { name: "إعادة إرسال الرمز" }));

    await waitFor(() => {
      expect(screen.getByText(FCM_DEVICE_ID_UNAVAILABLE_MSG)).toBeInTheDocument();
    });
    expect(apiFetch).not.toHaveBeenCalled();
  });

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

    render(<VerifyCodeForm />);

    await user.type(screen.getByPlaceholderText("123456"), "123456");
    await user.click(screen.getByRole("button", { name: "تأكيد الرمز" }));

    expect(
      await screen.findByText("رمز التحقق غير صحيح، حاول مرة أخرى"),
    ).toBeInTheDocument();
  });

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

    render(<VerifyCodeForm />);

    await user.click(screen.getByRole("button", { name: "إعادة إرسال الرمز" }));

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

  it("shows a success message when resending the code succeeds", async () => {
    const user = userEvent.setup();
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      options?.needVerifyCase?.({ key: "needVerify" });
      return { key: "needVerify" };
    });

    render(<VerifyCodeForm />);

    await user.click(screen.getByRole("button", { name: "إعادة إرسال الرمز" }));

    expect(
      await screen.findByText("تم إرسال رمز التحقق مرة أخرى إلى بريدك الإلكتروني"),
    ).toBeInTheDocument();
  });

  it("shows code validation and leaves auth untouched on success without data", async () => {
    const user = userEvent.setup();
    render(<VerifyCodeForm />);
    await user.click(screen.getByRole("button", { name: "تأكيد الرمز" }));
    expect(await screen.findByText("الرجاء إدخال رمز التحقق")).toBeInTheDocument();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope = { key: "success" } as const;
      options?.successCase?.(envelope);
      return envelope;
    });
    await user.type(screen.getByPlaceholderText("123456"), "123456");
    await user.click(screen.getByRole("button", { name: "تأكيد الرمز" }));
    await waitFor(() => expect(apiFetch).toHaveBeenCalled());
    expect(setAuth).not.toHaveBeenCalled();
    expect(replace).not.toHaveBeenCalled();
  });
});
