import { beforeEach, describe, expect, it, vi } from "vitest";

const { mockRedirect, mockAxiosPost, mockApi } = vi.hoisted(() => ({
  mockRedirect: vi.fn(),
  mockAxiosPost: vi.fn(),
  mockApi: {
    get: vi.fn(),
    post: vi.fn(),
  },
}));

vi.mock("$env/static/public", () => ({
  PUBLIC_API_URL: "https://api.example.com",
}));

vi.mock("$app/environment", () => ({
  browser: false,
}));

vi.mock("@sveltejs/kit", () => ({
  redirect: mockRedirect,
}));

vi.mock("axios", () => ({
  default: { post: mockAxiosPost },
  post: mockAxiosPost,
}));

vi.mock("$lib/optima-api/axios", () => ({
  default: mockApi,
  api: mockApi,
}));

vi.mock("socket.io-client", () => ({
  io: vi.fn(),
}));

import { user } from "./user";

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

  it("isLoggedInServer returns true when accessToken cookie exists", () => {
    const mockEvent = {
      cookies: {
        get: vi.fn().mockReturnValue("token"),
      },
    } as any;

    expect(user.isLoggedInServer(mockEvent)).toBe(true);
  });

  it("isLoggedInServer returns false when accessToken cookie is missing", () => {
    const mockEvent = {
      cookies: {
        get: vi.fn().mockReturnValue(undefined),
      },
    } as any;

    expect(user.isLoggedInServer(mockEvent)).toBe(false);
  });

  it("isLoggedIn throws an error when called without event", () => {
    expect(() => user.isLoggedIn()).toThrow(
      "user.isLoggedIn() should not be called without a request event. Use isLoggedInServer() instead."
    );
  });

  it("refreshSession posts refresh header and returns token payload", async () => {
    mockAxiosPost.mockResolvedValueOnce({
      data: {
        data: {
          accessToken: "new-access",
          refreshToken: "new-refresh",
        },
      },
    });

    const result = await user.refreshSession("refresh-123");

    expect(mockAxiosPost).toHaveBeenCalledWith(
      "https://api.example.com/v1/auth/refresh",
      {},
      {
        headers: {
          "x-refresh-token": "refresh-123",
        },
      }
    );
    expect(result).toEqual({
      accessToken: "new-access",
      refreshToken: "new-refresh",
    });
  });

  it("fetchInfo and checkPermissions call expected endpoints", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "me" } } });
    mockApi.post.mockResolvedValueOnce({ data: { data: { results: [] } } });

    await user.fetchInfo("token");
    await user.checkPermissions("token", ["company.read"]);

    expect(mockApi.get).toHaveBeenCalledWith("/v1/user/@me", {
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/user/@me/check-permission",
      { permissions: ["company.read"] },
      { headers: { Authorization: "Bearer token" } }
    );
  });

  it("logout clears auth cookies and redirects", () => {
    const deleteCookie = vi.fn();
    const fakeRedirect = { status: 303, location: "/login" };
    mockRedirect.mockReturnValueOnce(fakeRedirect);

    const result = user.logout({
      cookies: { delete: deleteCookie },
    } as any);

    expect(deleteCookie).toHaveBeenCalledWith("accessToken", { path: "/" });
    expect(deleteCookie).toHaveBeenCalledWith("refreshToken", { path: "/" });
    expect(mockRedirect).toHaveBeenCalledWith(303, "/login");
    expect(result).toBe(fakeRedirect);
  });

  it("awaitAuthCallback resolves when polling returns 200 with tokens", async () => {
    const mockFetch = vi
      .fn()
      .mockResolvedValueOnce({
        status: 202,
      })
      .mockResolvedValueOnce({
        status: 200,
        json: vi.fn().mockResolvedValue({
          data: { accessToken: "access", refreshToken: "refresh" },
        }),
      });
    vi.stubGlobal("fetch", mockFetch);

    const result = await user.awaitAuthCallback("cb-key");

    expect(result).toEqual({ accessToken: "access", refreshToken: "refresh" });
    expect(mockFetch).toHaveBeenCalledWith(
      "https://api.example.com/v1/auth/callback/cb-key"
    );

    vi.unstubAllGlobals();
  });

  it("awaitAuthCallback rejects when polling returns unexpected status", async () => {
    const mockFetch = vi.fn().mockResolvedValue({ status: 500 });
    vi.stubGlobal("fetch", mockFetch);

    await expect(user.awaitAuthCallback("cb-key")).rejects.toThrow(
      "Unexpected status 500"
    );

    vi.unstubAllGlobals();
  });
});
