
const LinkedInAdapter = require('../../../lib/Adapters/Auth/linkedin').default;
describe('LinkedInAdapter', function () {
  let adapter;
  const validOptions = {
    clientId: 'validClientId',
    clientSecret: 'validClientSecret',
    enableInsecureAuth: false,
  };

  beforeEach(function () {
    adapter = new LinkedInAdapter.constructor();
  });

  describe('Test configuration errors', function () {
    it('should throw error for missing options', function () {
      const invalidOptions = [null, undefined, {}, { clientId: 'validClientId' }];

      for (const options of invalidOptions) {
        expect(() => {
          adapter.validateOptions(options);
        }).toThrow();
      }
    });

    it('should validate options successfully with valid parameters', function () {
      expect(() => {
        adapter.validateOptions(validOptions);
      }).not.toThrow();
      expect(adapter.clientId).toBe(validOptions.clientId);
      expect(adapter.clientSecret).toBe(validOptions.clientSecret);
      expect(adapter.enableInsecureAuth).toBe(validOptions.enableInsecureAuth);
    });
  });

  describe('Test beforeFind', function () {
    it('should throw error for invalid payload', async function () {
      adapter.enableInsecureAuth = true;

      const payloads = [{}, { access_token: null }];

      for (const payload of payloads) {
        await expectAsync(adapter.beforeFind(payload)).toBeRejectedWith(
          new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LinkedIn auth is invalid for this user.')
        );
      }
    });

    it('should process secure payload and set auth data', async function () {
      spyOn(adapter, 'getAccessTokenFromCode').and.returnValue(
        Promise.resolve('validToken')
      );
      spyOn(adapter, 'getUserFromAccessToken').and.returnValue(
        Promise.resolve({ id: 'validUserId' })
      );

      const authData = { code: 'validCode', redirect_uri: 'http://example.com', is_mobile_sdk: false };

      await adapter.beforeFind(authData);

      expect(authData.access_token).toBe('validToken');
      expect(authData.id).toBe('validUserId');
    });

    it('should validate insecure auth and match user id', async function () {
      adapter.enableInsecureAuth = true;
      spyOn(adapter, 'getUserFromAccessToken').and.returnValue(
        Promise.resolve({ id: 'validUserId' })
      );

      const authData = { access_token: 'validToken', id: 'validUserId', is_mobile_sdk: false };

      await expectAsync(adapter.beforeFind(authData)).toBeResolved();
    });

    it('should throw error if insecure auth user id does not match', async function () {
      adapter.enableInsecureAuth = true;
      spyOn(adapter, 'getUserFromAccessToken').and.returnValue(
        Promise.resolve({ id: 'invalidUserId' })
      );

      const authData = { access_token: 'validToken', id: 'validUserId', is_mobile_sdk: false };

      await expectAsync(adapter.beforeFind(authData)).toBeRejectedWith(
        new Error('LinkedIn auth is invalid for this user.')
      );
    });
  });

  describe('Test getUserFromAccessToken', function () {
    it('should fetch user successfully', async function () {
      mockFetch([
        {
          url: 'https://api.linkedin.com/v2/me',
          method: 'GET',
          response: {
            ok: true,
            json: () => Promise.resolve({ id: 'validUserId' }),
          },
        },
      ]);

      const user = await adapter.getUserFromAccessToken('validToken', false);

      expect(global.fetch).toHaveBeenCalledWith('https://api.linkedin.com/v2/me', {
        headers: {
          Authorization: `Bearer validToken`,
          'x-li-format': 'json',
          'x-li-src': undefined,
        },
        method: 'GET',
      });
      expect(user).toEqual({ id: 'validUserId' });
    });

    it('should throw error for invalid response', async function () {
      mockFetch([
        {
          url: 'https://api.linkedin.com/v2/me',
          method: 'GET',
          response: {
            ok: false,
          },
        },
      ]);

      await expectAsync(adapter.getUserFromAccessToken('invalidToken', false)).toBeRejectedWith(
        new Error('LinkedIn API request failed.')
      );
    });
  });

  describe('Test getAccessTokenFromCode', function () {
    it('should fetch token successfully', async function () {
      mockFetch([
        {
          url: 'https://www.linkedin.com/oauth/v2/accessToken',
          method: 'POST',
          response: {
            ok: true,
            json: () => Promise.resolve({ access_token: 'validToken' }),
          },
        },
      ]);

      const tokenResponse = await adapter.getAccessTokenFromCode('validCode', 'http://example.com');

      expect(global.fetch).toHaveBeenCalledWith('https://www.linkedin.com/oauth/v2/accessToken', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: jasmine.any(URLSearchParams),
      });
      expect(tokenResponse).toEqual('validToken');
    });

    it('should throw error for invalid response', async function () {
      mockFetch([
        {
          url: 'https://www.linkedin.com/oauth/v2/accessToken',
          method: 'POST',
          response: {
            ok: false,
          },
        },
      ]);

      await expectAsync(
        adapter.getAccessTokenFromCode('invalidCode', 'http://example.com')
      ).toBeRejectedWith(new Error('LinkedIn API request failed.'));
    });
  });

  describe('Test validate methods', function () {
    const authData = { id: 'validUserId', access_token: 'validToken' };

    it('validateLogin should return user id', function () {
      const result = adapter.validateLogin(authData);
      expect(result).toEqual({ id: 'validUserId' });
    });

    it('validateSetUp should return user id', function () {
      const result = adapter.validateSetUp(authData);
      expect(result).toEqual({ id: 'validUserId' });
    });

    it('validateUpdate should return user id', function () {
      const result = adapter.validateUpdate(authData);
      expect(result).toEqual({ id: 'validUserId' });
    });

    it('afterFind should return user id', function () {
      const result = adapter.afterFind(authData);
      expect(result).toEqual({ id: 'validUserId' });
    });
  });

  describe('LinkedInAdapter E2E Test', function () {
    beforeEach(async function () {
      await reconfigureServer({
        auth: {
          linkedin: {
            clientId: 'validClientId',
            clientSecret: 'validClientSecret',
          },
        },
      });
    });

    it('should log in user using LinkedIn adapter successfully (secure)', async function () {
      mockFetch([
        {
          url: 'https://www.linkedin.com/oauth/v2/accessToken',
          method: 'POST',
          response: {
            ok: true,
            json: () =>
              Promise.resolve({
                access_token: 'mockAccessToken123',
              }),
          },
        },
        {
          url: 'https://api.linkedin.com/v2/me',
          method: 'GET',
          response: {
            ok: true,
            json: () =>
              Promise.resolve({
                id: 'mockUserId',
              }),
          },
        },
      ]);

      const authData = { code: 'validCode', redirect_uri: 'https://example.com/callback' };
      const user = await Parse.User.logInWith('linkedin', { authData });

      expect(user.id).toBeDefined();
      expect(global.fetch).toHaveBeenCalledWith(
        'https://www.linkedin.com/oauth/v2/accessToken',
        jasmine.any(Object)
      );
      expect(global.fetch).toHaveBeenCalledWith(
        'https://api.linkedin.com/v2/me',
        jasmine.any(Object)
      );
    });

    it('should handle error when LinkedIn returns invalid user data', async function () {
      mockFetch([
        {
          url: 'https://www.linkedin.com/oauth/v2/accessToken',
          method: 'POST',
          response: {
            ok: true,
            json: () =>
              Promise.resolve({
                access_token: 'mockAccessToken123',
              }),
          },
        },
        {
          url: 'https://api.linkedin.com/v2/me',
          method: 'GET',
          response: {
            ok: false,
            statusText: 'Unauthorized',
          },
        },
      ]);

      const authData = { code: 'validCode', redirect_uri: 'https://example.com/callback' };

      await expectAsync(Parse.User.logInWith('linkedin', { authData })).toBeRejectedWithError(
        'LinkedIn API request failed.'
      );

      expect(global.fetch).toHaveBeenCalledWith(
        'https://www.linkedin.com/oauth/v2/accessToken',
        jasmine.any(Object)
      );
      expect(global.fetch).toHaveBeenCalledWith(
        'https://api.linkedin.com/v2/me',
        jasmine.any(Object)
      );
    });

    it('secure does not support insecure payload if not enabled', async function () {
      mockFetch();
      const authData = { id: 'mockUserId', access_token: 'mockAccessToken123' };
      await expectAsync(Parse.User.logInWith('linkedin', { authData })).toBeRejectedWithError(
        'LinkedIn code is required.'
      );

      expect(global.fetch).not.toHaveBeenCalled();
    });

    it('insecure mode supports insecure payload if enabled', async function () {
      await reconfigureServer({
        auth: {
          linkedin: {
            clientId: 'validClientId',
            clientSecret: 'validClientSecret',
            enableInsecureAuth: true,
          },
        },
      });

      mockFetch([
        {
          url: 'https://api.linkedin.com/v2/me',
          method: 'GET',
          response: {
            ok: true,
            json: () =>
              Promise.resolve({
                id: 'mockUserId',
              }),
          },
        },
      ]);

      const authData = { id: 'mockUserId', access_token: 'mockAccessToken123' };
      const user = await Parse.User.logInWith('linkedin', { authData });

      expect(user.id).toBeDefined();
      expect(global.fetch).toHaveBeenCalledWith(
        'https://api.linkedin.com/v2/me',
        jasmine.any(Object)
      );
    });
  });
});
