// __tests__/auth.test.ts
import * as client from "openid-client";
import {
  clientConfig,
  getClientConfig,
  Auth,
  Logout,
  Callback,
} from "../../../src/features/User/oidcAuth";
import * as apiUtils from "../../../src/utils/apiUtils";

clientConfig.url = "https://example.com";
const localAdress = "https://local.exemple.com";

describe("OpenID Client Auth Module", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    jest.spyOn(apiUtils, "getLocation").mockImplementation(() => localAdress);
  });

  describe("getClientConfig", () => {
    it("should call discovery with clientConfig.url", async () => {
      const discoveryMock = client.discovery as jest.Mock;
      discoveryMock.mockResolvedValue("discoveredClient");

      const result = await getClientConfig();

      expect(discoveryMock).toHaveBeenCalledWith(
        new URL(clientConfig.url),
        clientConfig.client_id
      );
      expect(result).toBe("discoveredClient");
    });
  });

  describe("Auth", () => {
    it("should generate PKCE and build authorization URL with PKCE", async () => {
      (client.randomPKCECodeVerifier as jest.Mock).mockReturnValue(
        "verifier123"
      );
      (client.calculatePKCECodeChallenge as jest.Mock).mockResolvedValue(
        "challenge123"
      );

      // Mock discovery returning an object with serverMetadata()
      const discoveredClient = {
        serverMetadata: jest.fn(() => ({
          supportsPKCE: () => true,
        })),
      };
      (client.discovery as jest.Mock).mockResolvedValue(discoveredClient);

      (client.buildAuthorizationUrl as jest.Mock).mockReturnValue(
        "https://auth.url"
      );

      const result = await Auth();

      expect(client.randomPKCECodeVerifier).toHaveBeenCalled();
      expect(client.calculatePKCECodeChallenge).toHaveBeenCalledWith(
        "verifier123"
      );
      expect(client.buildAuthorizationUrl).toHaveBeenCalledWith(
        discoveredClient,
        expect.objectContaining({
          code_challenge: "challenge123",
          code_challenge_method: clientConfig.code_challenge_method,
          redirect_uri: clientConfig.redirect_uri,
          scope: clientConfig.scope,
        })
      );
      expect(result).toEqual({
        redirectTo: "https://auth.url",
        code_verifier: "verifier123",
        state: undefined,
      });
    });

    it("should generate state when PKCE not supported", async () => {
      (client.randomPKCECodeVerifier as jest.Mock).mockReturnValue(
        "verifier123"
      );
      (client.calculatePKCECodeChallenge as jest.Mock).mockResolvedValue(
        "challenge123"
      );

      const discoveredClient = {
        serverMetadata: jest.fn(() => ({
          supportsPKCE: () => false,
        })),
      };
      (client.discovery as jest.Mock).mockResolvedValue(discoveredClient);
      (client.randomState as jest.Mock).mockReturnValue("state123");
      (client.buildAuthorizationUrl as jest.Mock).mockReturnValue(
        "https://auth.url"
      );

      const result = await Auth();

      expect(client.randomState).toHaveBeenCalled();
      expect(result.state).toBe("state123");
      expect(result.redirectTo).toBe("https://auth.url");
    });
  });

  describe("Logout", () => {
    it("should build end session URL", async () => {
      const discoveredClient = {};
      (client.discovery as jest.Mock).mockResolvedValue(discoveredClient);
      (client.buildEndSessionUrl as jest.Mock).mockReturnValue(
        "https://logout.url"
      );

      const result = await Logout();

      expect(client.buildEndSessionUrl).toHaveBeenCalledWith(discoveredClient, {
        post_logout_redirect_uri: clientConfig.post_logout_redirect_uri,
      });
      expect(result).toBe("https://logout.url");
    });
  });

  describe("Callback", () => {
    it("should perform authorization code grant and fetch user info", async () => {
      const discoveredClient = {};
      (client.discovery as jest.Mock).mockResolvedValue(discoveredClient);

      const mockTokenSet = {
        access_token: "access123",
        claims: jest.fn(() => ({ sub: "user123" })),
      };
      (client.authorizationCodeGrant as jest.Mock).mockResolvedValue(
        mockTokenSet
      );
      (client.fetchUserInfo as jest.Mock).mockResolvedValue({ name: "User" });
      const result = await Callback("verifier123", "state123");

      expect(client.authorizationCodeGrant).toHaveBeenCalledWith(
        discoveredClient,
        new URL(localAdress),
        { pkceCodeVerifier: "verifier123", expectedState: "state123" }
      );

      expect(client.fetchUserInfo).toHaveBeenCalledWith(
        discoveredClient,
        "access123",
        "user123"
      );

      expect(result).toEqual({
        tokenSet: mockTokenSet,
        userinfo: { name: "User" },
      });
    });

    it("should catch and log errors", async () => {
      const error = new Error("fail");
      (client.discovery as jest.Mock).mockResolvedValue({});
      (client.authorizationCodeGrant as jest.Mock).mockRejectedValue(error);
      const consoleErrorSpy = jest
        .spyOn(console, "error")
        .mockImplementation(() => {});

      const result = await Callback("verifier", "state");

      expect(consoleErrorSpy).toHaveBeenCalledWith("Token grant error:", error);
      expect(result).toBeUndefined();

      consoleErrorSpy.mockRestore();
    });
  });
});
