import { TestBed } from '@/testing';
import { firstValueFrom, of } from 'rxjs';
import { vi } from 'vitest';
import { JwkExtractor } from '../extractors/jwk.extractor';
import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { CryptoService } from '../utils/crypto/crypto.service';
import { TokenHelperService } from '../utils/tokenHelper/token-helper.service';
import { JwkWindowCryptoService } from './jwk-window-crypto.service';
import { JwtWindowCryptoService } from './jwt-window-crypto.service';
import { TokenValidationService } from './token-validation.service';

describe('TokenValidationService', () => {
  let tokenValidationService: TokenValidationService;
  let tokenHelperService: TokenHelperService;
  let jwtWindowCryptoService: JwtWindowCryptoService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        TokenValidationService,
        mockProvider(LoggerService),
        mockProvider(TokenHelperService),
        JwkExtractor,
        JwkWindowCryptoService,
        JwtWindowCryptoService,
        CryptoService,
      ],
    });
    tokenValidationService = TestBed.inject(TokenValidationService);
    tokenHelperService = TestBed.inject(TokenHelperService);
    jwtWindowCryptoService = TestBed.inject(JwtWindowCryptoService);
  });

  it('should create', () => {
    expect(tokenValidationService).toBeTruthy();
  });

  describe('validateIdTokenAud', () => {
    it('returns true if aud is string and passed aud matches idToken.aud', () => {
      const dataIdToken = { aud: 'banana' };
      const valueTrue = tokenValidationService.validateIdTokenAud(
        dataIdToken,
        'banana',
        { configId: 'configId1' }
      );

      expect(valueTrue).toEqual(true);
    });

    it('returns false if aud is string and passed aud does not match idToken.aud', () => {
      const dataIdToken = { aud: 'banana' };

      const valueFalse = tokenValidationService.validateIdTokenAud(
        dataIdToken,
        'bananammmm',
        { configId: 'configId1' }
      );

      expect(valueFalse).toEqual(false);
    });

    it('returns true if aud is string array and passed aud is included in the array', () => {
      const dataIdToken = {
        aud: ['banana', 'apple', 'https://nice.dom'],
      };

      const audValidTrue = tokenValidationService.validateIdTokenAud(
        dataIdToken,
        'apple',
        { configId: 'configId1' }
      );

      expect(audValidTrue).toEqual(true);
    });

    it('returns false if aud is string array and passed aud is NOT included in the array', () => {
      const dataIdToken = {
        aud: ['banana', 'apple', 'https://nice.dom'],
      };

      const audValidFalse = tokenValidationService.validateIdTokenAud(
        dataIdToken,
        'https://nice.domunnnnnnkoem',
        {
          configId: 'configId1',
        }
      );

      expect(audValidFalse).toEqual(false);
    });
  });

  describe('validateIdTokenNonce', () => {
    it('should validate id token nonce after code grant when match', () => {
      expect(
        tokenValidationService.validateIdTokenNonce(
          { nonce: 'test1' },
          'test1',
          false,
          { configId: 'configId1' }
        )
      ).toBe(true);
    });

    it('should not validate id token nonce after code grant when no match', () => {
      expect(
        tokenValidationService.validateIdTokenNonce(
          { nonce: 'test1' },
          'test2',
          false,
          { configId: 'configId1' }
        )
      ).toBe(false);
    });

    it('should validate id token nonce after refresh token grant when undefined and no ignore', () => {
      expect(
        tokenValidationService.validateIdTokenNonce(
          { nonce: undefined },
          TokenValidationService.refreshTokenNoncePlaceholder,
          false,
          {
            configId: 'configId1',
          }
        )
      ).toBe(true);
    });

    it('should validate id token nonce after refresh token grant when undefined and ignore', () => {
      expect(
        tokenValidationService.validateIdTokenNonce(
          { nonce: undefined },
          TokenValidationService.refreshTokenNoncePlaceholder,
          true,
          {
            configId: 'configId1',
          }
        )
      ).toBe(true);
    });

    it('should validate id token nonce after refresh token grant when defined and ignore', () => {
      expect(
        tokenValidationService.validateIdTokenNonce(
          { nonce: 'test1' },
          TokenValidationService.refreshTokenNoncePlaceholder,
          true,
          {
            configId: 'configId1',
          }
        )
      ).toBe(true);
    });

    it('should not validate id token nonce after refresh token grant when defined and no ignore', () => {
      expect(
        tokenValidationService.validateIdTokenNonce(
          { nonce: 'test1' },
          TokenValidationService.refreshTokenNoncePlaceholder,
          false,
          {
            configId: 'configId1',
          }
        )
      ).toBe(false);
    });
  });

  describe('validateIdTokenAzpExistsIfMoreThanOneAud', () => {
    it('returns false if aud is array, öength is bigger than 1 and has no azp property', () => {
      const dataIdToken = {
        aud: ['one', 'two'],
      };
      const result =
        tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
          dataIdToken
        );

      expect(result).toBe(false);
    });

    it('returns false if aud is array, ength is bigger than 1 and has no azp property', () => {
      const result =
        tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(null);

      expect(result).toBe(false);
    });
  });

  describe('validateIdTokenAzpValid', () => {
    it('returns true dataIdToken param is null', () => {
      const result = tokenValidationService.validateIdTokenAzpValid(null, '');

      expect(result).toBe(true);
    });

    it('returns false when aud is an array and client id is NOT in the aud array', () => {
      const dataIdToken = {
        aud: [
          'banana',
          'apple',
          '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
        ],
        azp: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
      };

      const azpInvalid = tokenValidationService.validateIdTokenAzpValid(
        dataIdToken,
        'bananammmm'
      );

      expect(azpInvalid).toEqual(false);
    });

    it('returns true when aud is an array and client id is in the aud array', () => {
      const dataIdToken = {
        aud: [
          'banana',
          'apple',
          '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
        ],
        azp: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
      };

      const azpValid = tokenValidationService.validateIdTokenAzpValid(
        dataIdToken,
        '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com'
      );

      expect(azpValid).toEqual(true);
    });

    it('returns true if ID token has no azp property', () => {
      const dataIdToken = {
        noAzpProperty: 'something',
      };

      const azpValid = tokenValidationService.validateIdTokenAzpValid(
        dataIdToken,
        'bananammmm'
      );

      expect(azpValid).toEqual(true);
    });
  });

  describe('validateIdTokenAzpExistsIfMoreThanOneAud', () => {
    it('returns true if aud is array and aud contains azp', () => {
      const dataIdToken = {
        aud: [
          'banana',
          'apple',
          '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
        ],
        azp: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
      };

      const valueTrue =
        tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
          dataIdToken
        );

      expect(valueTrue).toEqual(true);
    });

    it('returns true if aud is array but only has one item', () => {
      const dataIdToken = {
        aud: ['banana'],
      };

      const valueTrue =
        tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
          dataIdToken
        );

      expect(valueTrue).toEqual(true);
    });

    it('returns true if aud is NOT an array', () => {
      const dataIdToken = {
        aud: 'banana',
      };
      const valueTrue =
        tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
          dataIdToken
        );

      expect(valueTrue).toEqual(true);
    });
  });

  describe('validateRequiredIdToken', () => {
    it('returns false if property iat is missing', () => {
      const decodedIdToken = {
        iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
        sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
        aud: 'bad',
        exp: 1589210086,
        // iat: 1589206486,
      };

      const result = tokenValidationService.validateRequiredIdToken(
        decodedIdToken,
        { configId: 'configId1' }
      );

      expect(result).toEqual(false);
    });

    it('returns false if property exp is missing', () => {
      const decodedIdToken = {
        iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
        sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
        aud: 'bad',
        // exp: 1589210086,
        iat: 1589206486,
      };

      const result = tokenValidationService.validateRequiredIdToken(
        decodedIdToken,
        { configId: 'configId1' }
      );

      expect(result).toEqual(false);
    });

    it('returns false if property aud is missing', () => {
      const decodedIdToken = {
        iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
        sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
        // aud: 'bad',
        exp: 1589210086,
        iat: 1589206486,
      };

      const result = tokenValidationService.validateRequiredIdToken(
        decodedIdToken,
        { configId: 'configId1' }
      );

      expect(result).toEqual(false);
    });

    it('returns false if property sub is missing', () => {
      const decodedIdToken = {
        iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
        // sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
        aud: 'bad',
        exp: 1589210086,
        iat: 1589206486,
      };

      const result = tokenValidationService.validateRequiredIdToken(
        decodedIdToken,
        { configId: 'configId1' }
      );

      expect(result).toEqual(false);
    });

    it('returns false if property iss is missing', () => {
      const decodedIdToken = {
        // iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
        sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
        aud: 'bad',
        exp: 1589210086,
        iat: 1589206486,
      };

      const result = tokenValidationService.validateRequiredIdToken(
        decodedIdToken,
        { configId: 'configId1' }
      );

      expect(result).toEqual(false);
    });

    it('returns true if all is valid', () => {
      const decodedIdToken = {
        iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
        sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
        aud: 'bad',
        exp: 1589210086,
        iat: 1589206486,
      };

      const result = tokenValidationService.validateRequiredIdToken(
        decodedIdToken,
        { configId: 'configId1' }
      );

      expect(result).toEqual(true);
    });
  });

  describe('validateIdTokenIss', () => {
    it('returns true if issuer matches iss in idToken', () => {
      const decodedIdToken = {
        iss: 'xc',
      };

      const valueTrue = tokenValidationService.validateIdTokenIss(
        decodedIdToken,
        'xc',
        { configId: 'configId1' }
      );

      expect(valueTrue).toEqual(true);
    });

    it('returns false if issuer does not match iss in idToken', () => {
      const decodedIdToken = {
        iss: 'xc',
      };

      const valueFalse = tokenValidationService.validateIdTokenIss(
        decodedIdToken,
        'xcjjjj',
        { configId: 'configId1' }
      );

      expect(valueFalse).toEqual(false);
    });
  });

  describe('validateIdTokenIatMaxOffset', () => {
    it('returns true if validationIsDisabled', () => {
      const result = tokenValidationService.validateIdTokenIatMaxOffset(
        null,
        0,
        true,
        { configId: 'configId1' }
      );

      expect(result).toBe(true);
    });

    it('returns false if dataIdToken has no property "iat"', () => {
      const dataIdToken = {
        notIat: 'test',
      };
      const result = tokenValidationService.validateIdTokenIatMaxOffset(
        dataIdToken,
        0,
        false,
        { configId: 'configId1' }
      );

      expect(result).toBe(false);
    });

    it('returns true if time is Mon Jan 19 1970 10:26:46 GMT+0100, and the offset is big like 500000000000 seconds', () => {
      const decodedIdToken = {
        iat: 1589206486, // Mon Jan 19 1970 10:26:46 GMT+0100 (Central European Standard Time)
      };

      const valueTrue = tokenValidationService.validateIdTokenIatMaxOffset(
        decodedIdToken,
        500000000000,
        false,
        { configId: 'configId1' }
      );

      expect(valueTrue).toEqual(true);
    });

    it('returns false if time is Sat Nov 09 1985 02:47:57 GMT+0100, and the offset is 0 seconds', () => {
      const decodedIdTokenNegIat = {
        iat: 500348877430, // Sat Nov 09 1985 02:47:57 GMT+0100 (Central European Standard Time)
      };
      const valueFalseNeg = tokenValidationService.validateIdTokenIatMaxOffset(
        decodedIdTokenNegIat,
        0,
        false,
        { configId: 'configId1' }
      );

      expect(valueFalseNeg).toEqual(false);
    });

    it('returns true if time is Mon Jan 19 1970 10:26:46 GMT+0100, and the offset is small like 5 seconds', () => {
      const decodedIdToken = {
        iat: 1589206486, // Mon Jan 19 1970 10:26:46 GMT+0100 (Central European Standard Time)
      };
      const valueFalse = tokenValidationService.validateIdTokenIatMaxOffset(
        decodedIdToken,
        5,
        false,
        { configId: 'configId1' }
      );

      expect(valueFalse).toEqual(false);
    });
  });

  describe('validateSignatureIdToken', () => {
    it('returns false if no kwtKeys are passed', async () => {
      const valueFalse$ = tokenValidationService.validateSignatureIdToken(
        'some-id-token',
        null,
        { configId: 'configId1' }
      );

      const valueFalse = await firstValueFrom(valueFalse$);
      expect(valueFalse).toEqual(false);
    });

    it('returns true if no idToken is passed', async () => {
      const valueFalse$ = tokenValidationService.validateSignatureIdToken(
        null as any,
        'some-jwt-keys',
        { configId: 'configId1' }
      );

      const valueFalse = await firstValueFrom(valueFalse$);
      expect(valueFalse).toEqual(true);
    });

    it('returns false if jwtkeys has no keys-property', async () => {
      const valueFalse$ = tokenValidationService.validateSignatureIdToken(
        'some-id-token',
        { notKeys: '' },
        { configId: 'configId1' }
      );

      const valueFalse = await firstValueFrom(valueFalse$);
      expect(valueFalse).toEqual(false);
    });

    it('returns false if header data has no header data', async () => {
      vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({});

      const jwtKeys = {
        keys: 'someThing',
      };

      const valueFalse$ = tokenValidationService.validateSignatureIdToken(
        'some-id-token',
        jwtKeys,
        { configId: 'configId1' }
      );

      const valueFalse = await firstValueFrom(valueFalse$);
      expect(valueFalse).toEqual(false);
    });

    it('returns false if header data alg property does not exist in keyalgorithms', async () => {
      vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({
        alg: 'NOT SUPPORTED ALG',
      });

      const jwtKeys = {
        keys: 'someThing',
      };

      const valueFalse$ = tokenValidationService.validateSignatureIdToken(
        'some-id-token',
        jwtKeys,
        { configId: 'configId1' }
      );

      const valueFalse = await firstValueFrom(valueFalse$);
      expect(valueFalse).toEqual(false);
    });

    it('returns false if header data has kid property and jwtKeys has same kid property but they are not valid with the token', async () => {
      const kid = '5626CE6A8F4F5FCD79C6642345282CA76D337548';

      vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({
        alg: 'RS256',
        kid,
      });
      vi.spyOn(tokenHelperService, 'getSignatureFromToken').mockReturnValue('');

      const jwtKeys = {
        keys: [
          {
            kty: 'RSA',
            use: 'sig',
            kid,
            x5t: 'VibOao9PX815xmQjRSgsp20zdUg',
            e: 'AQAB',
            n: 'uu3-HK4pLRHJHoEBzFhM516RWx6nybG5yQjH4NbKjfGQ8dtKy1BcGjqfMaEKF8KOK44NbAx7rtBKCO9EKNYkeFvcUzBzVeuu4jWG61XYdTekgv-Dh_Fj8245GocEkbvBbFW6cw-_N59JWqUuiCvb-EOfhcuubUcr44a0AQyNccYNpcXGRcMKy7_L1YhO0AMULqLDDVLFj5glh4TcJ2N5VnJedq1-_JKOxPqD1ni26UOQoWrW16G29KZ1_4Xxf2jX8TAq-4RJEHccdzgZVIO4F5B4MucMZGq8_jMCpiTUsUGDOAMA_AmjxIRHOtO5n6Pt0wofrKoAVhGh2sCTtaQf2Q',
            x5c: [
              'MIIDPzCCAiegAwIBAgIQF+HRVxLHII9IlOoQk6BxcjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBzdHMuZGV2LmNlcnQuY29tMB4XDTE5MDIyMDEwMTA0M1oXDTM5MDIyMDEwMTkyOVowGzEZMBcGA1UEAwwQc3RzLmRldi5jZXJ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrt/hyuKS0RyR6BAcxYTOdekVsep8mxuckIx+DWyo3xkPHbSstQXBo6nzGhChfCjiuODWwMe67QSgjvRCjWJHhb3FMwc1XrruI1hutV2HU3pIL/g4fxY/NuORqHBJG7wWxVunMPvzefSVqlLogr2/hDn4XLrm1HK+OGtAEMjXHGDaXFxkXDCsu/y9WITtADFC6iww1SxY+YJYeE3CdjeVZyXnatfvySjsT6g9Z4tulDkKFq1tehtvSmdf+F8X9o1/EwKvuESRB3HHc4GVSDuBeQeDLnDGRqvP4zAqYk1LFBgzgDAPwJo8SERzrTuZ+j7dMKH6yqAFYRodrAk7WkH9kCAwEAAaN/MH0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAtBgNVHREEJjAkghBzdHMuZGV2LmNlcnQuY29tghBzdHMuZGV2LmNlcnQuY29tMB0GA1UdDgQWBBQuyHxWP3je6jGMOmOiY+hz47r36jANBgkqhkiG9w0BAQsFAAOCAQEAKEHG7Ga6nb2XiHXDc69KsIJwbO80+LE8HVJojvITILz3juN6/FmK0HmogjU6cYST7m1MyxsVhQQNwJASZ6haBNuBbNzBXfyyfb4kr62t1oDLNwhctHaHaM4sJSf/xIw+YO+Qf7BtfRAVsbM05+QXIi2LycGrzELiXu7KFM0E1+T8UOZ2Qyv7OlCb/pWkYuDgE4w97ox0MhDpvgluxZLpRanOLUCVGrfFaij7gRAhjYPUY3vAEcD8JcFBz1XijU8ozRO6FaG4qg8/JCe+VgoWsMDj3sKB9g0ob6KCyG9L2bdk99PGgvXDQvMYCpkpZzG3XsxOINPd5p0gc209ZOoxTg==',
            ],
            alg: 'RS256',
          },
        ],
      };

      const valueFalse$ = tokenValidationService.validateSignatureIdToken(
        'someNOTMATCHINGIdToken',
        jwtKeys,
        { configId: 'configId1' }
      );

      const valueFalse = await firstValueFrom(valueFalse$);
      expect(valueFalse).toEqual(false);
    });

    it('should return true if valid input is provided', async () => {
      const idToken =
        'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2IiwiYXVkIjoibXlfY2xpZW50X2lkIiwiZXhwIjoxMzExMjgxOTcwLCJpYXQiOjEzMTEyODA5NzAsIm5hbWUiOiJKYW5lIERvZSIsImdpdmVuX25hbWUiOiJKYW5lIiwiZmFtaWx5X25hbWUiOiJEb2UiLCJiaXJ0aGRhdGUiOiIxOTkwLTEwLTMxIiwiZW1haWwiOiJqYW5lZG9lQGV4YW1wbGUuY29tIiwicGljdHVyZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vamFuZWRvZS9tZS5qcGcifQ.SY0ilps7yKYmYCc41zNOatfmAFhOtDYwuIT80qrHMl_4FEO2WFWSv-aDl4QfTSKY9A6MMP6xy0Z_8Kk7NeRwIV7FVScMLnPvVzs9pxza0e_rl6hmZLb5P5n4AEINwn46X9XmRB5W3EZO_x2LG65_g3NZFiPrzOC1Fs_6taJl7TfI8lOveYDoJyXCWYQMS3Oh5MM9S8W-Hc29_qJLH-kixm1S01qoICRPDGMRwhtAu1DHjwWQp9Ycfz6g3uyb7N1imBvI49t1CwWy02_mQ3g-7e7bOP1Ax2kgrwnJgsVBDULnyCZG9PE8T0CHZl_fErZtvbJJ0jdoZ1fyr48906am2w';
      const idTokenParts = idToken.split('.');
      const key = {
        kty: 'RSA',
        n: 'u1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0_IzW7yWR7QkrmBL7jTKEn5u-qKhbwKfBstIs-bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW_VDL5AaWTg0nLVkjRo9z-40RQzuVaE8AkAFmxZzow3x-VJYKdjykkJ0iT9wCS0DRTXu269V264Vf_3jvredZiKRkgwlL9xNAwxXFg0x_XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC-9aGVd-Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmw',
        e: 'AQAB',
        alg: 'RS256',
        kid: 'boop',
        use: 'sig',
      };
      const jwtKeys = {
        keys: [key],
      };

      vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({
        alg: 'RS256',
        typ: 'JWT',
      });
      vi.spyOn(tokenHelperService, 'getSigningInputFromToken').mockReturnValue(
        [idTokenParts[0], idTokenParts[1]].join('.')
      );
      vi.spyOn(tokenHelperService, 'getSignatureFromToken').mockReturnValue(
        idTokenParts[2]
      );

      const valueTrue$ = tokenValidationService.validateSignatureIdToken(
        idToken,
        jwtKeys,
        { configId: 'configId1' }
      );

      const valueTrue = await firstValueFrom(valueTrue$);
      expect(valueTrue).toEqual(true);
    });
  });

  describe('validateIdTokenAtHash', () => {
    it('returns true if sha is sha256 and generated hash equals atHash param', async () => {
      const accessToken = 'iGU3DhbPoDljiYtr0oepxi7zpT8BsjdU7aaXcdq-DPk';
      const atHash = '-ODC_7Go_UIUTC8nP4k2cA';

      const result$ = tokenValidationService.validateIdTokenAtHash(
        accessToken,
        atHash,
        '256',
        { configId: 'configId1' }
      );

      const result = await firstValueFrom(result$);
      expect(result).toEqual(true);
    });

    it('returns false if sha is sha256 and generated hash does not equal atHash param', async () => {
      const accessToken =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
      const atHash = 'bad';

      const result = await firstValueFrom(
        tokenValidationService.validateIdTokenAtHash(
          accessToken,
          atHash,
          '256',
          { configId: 'configId1' }
        )
      );

      expect(result).toEqual(false);
    });

    it('returns true if sha is sha256 and generated hash does equal atHash param', async () => {
      const accessToken =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
      const atHash = 'good';

      vi.spyOn(jwtWindowCryptoService, 'generateAtHash')
        .mockReturnValueOnce(of('notEqualsGood'))
        .mockReturnValueOnce(of('good'));

      const result$ = tokenValidationService.validateIdTokenAtHash(
        accessToken,
        atHash,
        '256',
        { configId: 'configId1' }
      );

      const result = await firstValueFrom(result$);
      expect(result).toEqual(true);
    });

    it('returns false if sha is sha384 and generated hash does not equal atHash param', async () => {
      const accessToken =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
      const atHash = 'bad';

      const result$ = tokenValidationService.validateIdTokenAtHash(
        accessToken,
        atHash,
        '384',
        { configId: 'configId1' }
      );

      const result = await firstValueFrom(result$);
      expect(result).toEqual(false);
    });

    it('returns false if sha is sha512 and generated hash does not equal atHash param', async () => {
      const accessToken =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
      const atHash = 'bad';

      const result$ = tokenValidationService.validateIdTokenAtHash(
        accessToken,
        atHash,
        '512',
        { configId: 'configId1' }
      );

      const result = await firstValueFrom(result$);
      expect(result).toEqual(false);
    });
  });

  describe('validateStateFromHashCallback', () => {
    it('returns true when state and localstate match', () => {
      const result = tokenValidationService.validateStateFromHashCallback(
        'sssd',
        'sssd',
        { configId: 'configId1' }
      );

      expect(result).toEqual(true);
    });

    it('returns false when state and local state do not match', () => {
      const result = tokenValidationService.validateStateFromHashCallback(
        'sssd',
        'bad',
        { configId: 'configId1' }
      );

      expect(result).toEqual(false);
    });
  });

  describe('validateIdTokenExpNotExpired', () => {
    it('returns false when getTokenExpirationDate returns null', () => {
      vi.spyOn(tokenHelperService, 'getTokenExpirationDate').mockReturnValue(
        null as unknown as Date
      );
      const notExpired = tokenValidationService.validateIdTokenExpNotExpired(
        'idToken',
        { configId: 'configId1' },
        0
      );

      expect(notExpired).toEqual(false);
    });

    it('returns false if token is not expired', () => {
      const idToken =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';

      const notExpired = tokenValidationService.validateIdTokenExpNotExpired(
        idToken,
        { configId: 'configId1' },
        0
      );

      expect(notExpired).toEqual(false);
    });
  });

  describe('validateAccessTokenNotExpired', () => {
    const testCases = [
      {
        // Mon Jan 19 1970 10:26:50 GMT+0100,
        date: new Date(1589210086),
        offsetSeconds: 0,
        expectedResult: false,
      },
      {
        // Sun Nov 01 2550 00:00:00 GMT+0100
        date: new Date(2550, 10),
        offsetSeconds: 0,
        expectedResult: true,
      },
      {
        date: null,
        offsetSeconds: 300,
        expectedResult: true,
      },
    ];

    for (const { date, offsetSeconds, expectedResult } of testCases) {
      it(`returns $expectedResultif ${date} is given with an offset of $offsetSeconds`, () => {
        const notExpired = tokenValidationService.validateAccessTokenNotExpired(
          date as Date,
          { configId: 'configId1' },
          offsetSeconds
        );

        expect(notExpired).toEqual(expectedResult);
      });
    }
  });

  describe('hasIdTokenExpired', () => {
    it('returns true if token has expired', () => {
      const idToken =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';

      const result = tokenValidationService.hasIdTokenExpired(idToken, {
        configId: 'configId1',
      });

      expect(result).toBe(true);
    });

    it('returns false if token has not expired using offset', () => {
      // expires 2050-02-12T08:02:30.823Z
      const tokenExpires = new Date('2050-02-12T08:02:30.823Z');
      const idToken =
        'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTMxMTY5NTAsImV4cCI6MjUyODI2NTc1MCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.GHxRo23NghUTTeZx6VIzTSf05JEeEn7z9YYyFLxWv6M';

      vi.spyOn(tokenHelperService, 'getTokenExpirationDate').mockReturnValue(
        tokenExpires
      );

      const result = tokenValidationService.hasIdTokenExpired(idToken, {
        configId: 'configId1',
      });

      expect(result).toBe(false);
    });
  });
});
