import {
  ConfigStatusResponseCode,
  IdentityProvider,
  LogoutResponseCode,
  TokenResponseCode,
  User,
} from '@axinom/mosaic-id-utils';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
  IdentityServiceClient,
  createIdentityServiceClient,
} from './IdentityServiceClient';

describe('IdentityServiceClient', () => {
  const tenantId = 'b6590a4b-d2fd-4a35-a05e-c22e1ffb1e36';
  const environmentId = 'afc7f50a-d595-43f2-b169-aa786fc6e50d';
  const idServiceAuthBaseUrl = 'https://id.service.axinom.com';
  let idServiceClient: IdentityServiceClient;

  const fetchRef = global.fetch;

  function mockFetchResponse<T>(response: T): void {
    global.fetch = vi.fn().mockImplementation(() =>
      Promise.resolve({
        json: () => Promise.resolve(response),
      }),
    );
  }

  beforeEach(() => {
    idServiceClient = createIdentityServiceClient({
      tenantId,
      environmentId,
      idServiceAuthBaseUrl,
    });
  });

  afterEach(() => {
    global.fetch = fetchRef;
  });

  describe('getConfiguration', () => {
    it.each([
      [
        'all IDPs enabled',
        {
          code: ConfigStatusResponseCode.SUCCESS,
          tenantId,
          environmentId,
          enabledIdentityProviders: [
            {
              idProvider: 'AX_AUTH',
              idProviderId: 'dd7583b7-c71b-4ee8-a2da-3b22bfa8b0b0',
              enabled: true,
              clientId: 'D6jT4fMD37NQHzGhAX2OM',
              customBrandingConfig: {
                displayName: 'AxAuth',
                idpImageUrl: 'https://idp.ax.com/axauth.png',
              },
            },
            {
              idProvider: 'GOOGLE',
              idProviderId: 'dd7583b7-c71b-4ee8-a2da-3b22bfa8b0b0',
              enabled: true,
              customBrandingConfig: {
                displayName: 'Google',
                idpImageUrl: 'https://idp.ax.com/google.png',
              },
            },
            {
              idProvider: 'AZURE_AD',
              idProviderId: 'dd7583b7-c71b-4ee8-a2da-3b22bfa8b0b0',
              enabled: true,
              customBrandingConfig: {
                displayName: 'Microsoft',
                idpImageUrl: 'https://idp.ax.com/microsoft.png',
              },
            },
            {
              idProvider: 'AXINOM',
              idProviderId: 'dd7583b7-c71b-4ee8-a2da-3b22bfa8b0b0',
              enabled: true,
              customBrandingConfig: {
                displayName: 'Axinom',
                idpImageUrl: 'https://idp.ax.com/axinom.png',
              },
            },
          ],
        },
        {
          status: 'SUCCESS',
          providers: [
            {
              idpId: 'AX_AUTH',
              displayName: 'AxAuth',
              idpImageUrl: 'https://idp.ax.com/axauth.png',
              enabled: true,
            },
            {
              idpId: 'GOOGLE',
              displayName: 'Google',
              idpImageUrl: 'https://idp.ax.com/google.png',
              enabled: true,
            },
            {
              idpId: 'AZURE_AD',
              displayName: 'Microsoft',
              idpImageUrl: 'https://idp.ax.com/microsoft.png',
              enabled: true,
            },
            {
              idpId: 'AXINOM',
              displayName: 'Axinom',
              idpImageUrl: 'https://idp.ax.com/axinom.png',
              enabled: true,
            },
          ],
          tenantId,
          environmentId,
        },
      ],
      [
        'only AxAuth is enabled',
        {
          code: ConfigStatusResponseCode.SUCCESS,
          tenantId,
          environmentId,
          enabledIdentityProviders: [
            {
              idProvider: 'AX_AUTH',
              idProviderId: 'dd7583b7-c71b-4ee8-a2da-3b22bfa8b0b0',
              enabled: true,
              clientId: 'D6jT4fMD37NQHzGhAX2OM',
              customBrandingConfig: {
                displayName: 'AxAuth',
                idpImageUrl: 'https://idp.ax.com/axauth.png',
              },
            },
          ],
        },
        {
          status: 'SUCCESS',
          providers: [
            {
              idpId: 'AX_AUTH',
              displayName: 'AxAuth',
              idpImageUrl: 'https://idp.ax.com/axauth.png',
              enabled: true,
            },
          ],
          tenantId,
          environmentId,
        },
      ],
      [
        'all IDPs disabled',
        {
          code: ConfigStatusResponseCode.SUCCESS,
          tenantId,
          environmentId,
          enabledIdentityProviders: [],
        },
        {
          status: 'SUCCESS',
          providers: [],
          tenantId,
          environmentId,
        },
      ],
    ])(
      'When getConfiguration is called and the response code is SUCCESS having %s, it should return the correct ID Service Configuration',
      async (_, fetchResponse, expectedResponse) => {
        // Arrange
        mockFetchResponse(fetchResponse);

        // Act
        const configResponse = await idServiceClient.getConfiguration();

        // Assert
        expect(configResponse).toEqual(expectedResponse);
      },
    );

    it.each([
      ConfigStatusResponseCode.ENVIRONMENT_NOT_ACTIVE,
      ConfigStatusResponseCode.ERROR,
      ConfigStatusResponseCode.IDP_MISCONFIGURATION,
      ConfigStatusResponseCode.TENANT_NOT_ACTIVE,
    ])(
      'When getConfiguration is called and the response code is %s, a response with the correct status code is returned',
      async (configResponseCode) => {
        // Arrange
        mockFetchResponse({
          code: configResponseCode,
          tenantId,
          environmentId,
          enabledIdentityProviders: [],
        });

        const configResponse = await idServiceClient.getConfiguration();
        expect(configResponse).toEqual({
          status: configResponseCode,
          tenantId,
          environmentId,
        });
      },
    );
  });

  describe('getToken', () => {
    const user: User = {
      id: 'user-id',
      name: 'user-name',
      email: 'user@ax.com',
      profilePictureUrl: 'https://avatar.com/user',
      token: {
        accessToken: 'access-token',
        expiresInSeconds: 36000,
        permissions: { testService: ['PERMISSION_A'] },
        tags: ['DE', 'LK'],
      },
    };
    it('When getToken is called with a Success code, TokenResponse with User details is given', async () => {
      // Arrange
      mockFetchResponse({
        code: TokenResponseCode.SUCCESS,
        user,
      });

      // Act
      const tokenResponse = await idServiceClient.getToken();

      // Assert
      expect(tokenResponse).toEqual({
        code: TokenResponseCode.SUCCESS,
        user,
      });
    });

    it('When getToken is called for a second time, token is returned from cache, without calling fetch', async () => {
      // Arrange
      mockFetchResponse({
        code: TokenResponseCode.SUCCESS,
        user,
      });

      // Act
      await idServiceClient.getToken();
      const tokenResponse = await idServiceClient.getToken();

      // Assert
      // fetch is only called once, although the getToken method is called twice.
      expect(global.fetch).toHaveBeenCalledTimes(1);
      expect(tokenResponse).toEqual({
        code: TokenResponseCode.SUCCESS,
        user,
      });
    });
  });

  describe('logoutUser and addTokenChangedHandler', () => {
    it('When logoutUser is called, and the response is Success, true is returned', async () => {
      // Arrange
      mockFetchResponse({
        code: LogoutResponseCode.SUCCESS,
      });
      const tokenChangedHandler = vi.fn();
      idServiceClient.addTokenChangedHandler(tokenChangedHandler);

      // Act
      const logoutResponse = await idServiceClient.logoutUser();

      // Assert
      expect(tokenChangedHandler).toHaveBeenCalledTimes(1);
      expect(logoutResponse).toBe(true);
    });

    it('When logoutUser is called, and the response is Error, false is returned', async () => {
      // Arrange
      mockFetchResponse({
        code: LogoutResponseCode.ERROR,
      });
      const tokenChangedHandler = vi.fn();
      idServiceClient.addTokenChangedHandler(tokenChangedHandler);

      // Act
      const logoutResponse = await idServiceClient.logoutUser();

      // Assert
      expect(tokenChangedHandler).toHaveBeenCalledTimes(1);
      expect(logoutResponse).toBe(false);
    });
  });

  describe('getIdpAuthUrl', () => {
    const originUrl = 'https://axiflix.ax.com';
    it.each([IdentityProvider.AZURE_AD, IdentityProvider.GOOGLE])(
      'When getIdpAuthUrl is called with ID Provider as %s, it returns the authentication URL for the given IDP',
      (idProvider) => {
        // Act
        const idpUrlResponse = idServiceClient.getIdpAuthUrl(
          idProvider,
          originUrl,
        );

        expect(idpUrlResponse).toBe(
          `${idServiceAuthBaseUrl}/${tenantId}/${environmentId}/auth?providerId=${idProvider}&originUrl=${originUrl}&idServiceClientUrl=${idServiceAuthBaseUrl}`,
        );
      },
    );
  });

  describe('removeTokenChangedHandler', () => {
    it('When removeTokenChangedHandler is called, the token changed handler is removed', async () => {
      // Arrange
      mockFetchResponse({
        code: LogoutResponseCode.SUCCESS,
      });
      const tokenChangedHandler = vi.fn();
      idServiceClient.addTokenChangedHandler(tokenChangedHandler);

      await idServiceClient.logoutUser();

      // Make sure the tokenChangedHandler is called before removing.
      expect(tokenChangedHandler).toHaveBeenCalledTimes(1);
      // Clear the count
      tokenChangedHandler.mockClear();

      // Act
      idServiceClient.removeTokenChangedHandler(tokenChangedHandler);

      // Assert
      await idServiceClient.logoutUser();
      expect(tokenChangedHandler).toHaveBeenCalledTimes(0);
    });
  });
});
