import '@angular/compiler';
import { Injector, runInInjectionContext } from '@angular/core';
import { Router } from '@angular/router';
import { of, Subject } from 'rxjs';
import { AuthenticationService } from '../../authentication/shared/authentication.service';
import { TokenStore } from '../../authentication/shared/token-store.service';
import { auroraGraphqlAuthHandlerFactory } from './aurora-graphql-auth.handler';

function setup(options: { refreshToken?: string } = {}) {
  const refreshToken = options.refreshToken ?? 'refresh-token-123';

  const signInUsingRefreshToken = vi.fn().mockReturnValue(of(true));
  const signOut = vi.fn().mockReturnValue(of(true));
  const navigate = vi.fn().mockResolvedValue(true);

  const injector = Injector.create({
    providers: [
      { provide: TokenStore, useValue: { refreshToken: () => refreshToken } },
      { provide: AuthenticationService, useValue: { signInUsingRefreshToken, signOut } },
      { provide: Router, useValue: { navigate } },
    ],
  });

  const handler = runInInjectionContext(injector, () => auroraGraphqlAuthHandlerFactory());

  return { handler, signInUsingRefreshToken, signOut, navigate };
}

describe('auroraGraphqlAuthHandlerFactory', () => {
  describe('getAuthorizationHeader', () => {
    it('returns a Basic header for client-authenticated operations', async () => {
      const { handler } = setup();
      expect(await handler.getAuthorizationHeader('oAuthCreateToken')).toMatch(/^Basic /);
      expect(await handler.getAuthorizationHeader('oAuthRevokeToken')).toMatch(/^Basic /);
    });

    it('returns null for user/Bearer operations', async () => {
      const { handler } = setup();
      expect(await handler.getAuthorizationHeader('iamPaginateUsers')).toBeNull();
      expect(await handler.getAuthorizationHeader(null)).toBeNull();
    });
  });

  describe('tryRefresh', () => {
    it('returns false without calling the service when no refresh token is present', async () => {
      const { handler, signInUsingRefreshToken } = setup({ refreshToken: '' });

      await expect(handler.tryRefresh()).resolves.toBe(false);
      expect(signInUsingRefreshToken).not.toHaveBeenCalled();
    });

    it('coalesces concurrent refreshes into a single in-flight call (single-flight)', async () => {
      const { handler, signInUsingRefreshToken } = setup();

      const results = await Promise.all([
        handler.tryRefresh(),
        handler.tryRefresh(),
        handler.tryRefresh(),
      ]);

      expect(signInUsingRefreshToken).toHaveBeenCalledTimes(1);
      expect(results).toEqual([true, true, true]);
    });

    it('starts a fresh refresh once the previous one has settled', async () => {
      const { handler, signInUsingRefreshToken } = setup();

      await handler.tryRefresh();
      await handler.tryRefresh();

      expect(signInUsingRefreshToken).toHaveBeenCalledTimes(2);
    });
  });

  describe('handleUnauthorized', () => {
    it('signs out and redirects to the sign-in route', async () => {
      const { handler, signOut, navigate } = setup();

      handler.handleUnauthorized();
      await Promise.resolve();

      expect(signOut).toHaveBeenCalledTimes(1);
      expect(navigate).toHaveBeenCalledWith(['/auth/sign-in']);
    });

    it('coalesces concurrent callers into a single sign-out + redirect', () => {
      const signOut$ = new Subject<boolean>();
      const signOut = vi.fn().mockReturnValue(signOut$);
      const navigate = vi.fn().mockResolvedValue(true);
      const injector = Injector.create({
        providers: [
          { provide: TokenStore, useValue: { refreshToken: () => 'rt' } },
          { provide: AuthenticationService, useValue: { signOut } },
          { provide: Router, useValue: { navigate } },
        ],
      });
      const handler = runInInjectionContext(injector, () => auroraGraphqlAuthHandlerFactory());

      // Three concurrent failed operations all route to handleUnauthorized while one is in flight.
      handler.handleUnauthorized();
      handler.handleUnauthorized();
      handler.handleUnauthorized();

      // Only ONE signOut runs (no fan-out of revoke mutations); no navigation until it settles.
      expect(signOut).toHaveBeenCalledTimes(1);
      expect(navigate).not.toHaveBeenCalled();

      // Once it settles, exactly one navigation happens and the guard resets for a later session.
      signOut$.next(true);
      signOut$.complete();
      expect(navigate).toHaveBeenCalledTimes(1);
      expect(navigate).toHaveBeenCalledWith(['/auth/sign-in']);
    });
  });
});
