import { OAuthRevokeTokenHandler } from '@app/o-auth/flows';
import { GqlThrottlerGuard } from '@aurora/modules/graphql';
import { Test, TestingModule } from '@nestjs/testing';
import type { Request } from 'express';
import { OAuthRevokeTokenResolver } from './o-auth-revoke-token.resolver';

/**
 * Encodes a `clientId:clientSecret` pair as HTTP Basic auth header value (RFC 6749 §2.3.1).
 */
const basic = (clientId: string, clientSecret: string): string =>
  `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;

const buildContext = (overrides: { authorization?: string } = {}): { req: Request } => ({
  req: {
    headers: {
      ...(overrides.authorization !== undefined ? { authorization: overrides.authorization } : {}),
    },
  } as unknown as Request,
});

describe('OAuthRevokeTokenResolver (transport — pure mapping)', () => {
  let resolver: OAuthRevokeTokenResolver;
  let handler: { main: jest.Mock };

  const buildModule = async (): Promise<void> => {
    handler = { main: jest.fn().mockResolvedValue({ revoked: true }) };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        OAuthRevokeTokenResolver,
        { provide: OAuthRevokeTokenHandler, useValue: handler },
      ],
    })
      // GqlThrottlerGuard is wired on the mutation. Bypass it here — rate-limit behavior is an
      // integration concern (verified by the guard's own spec), not a transport-mapping concern.
      .overrideGuard(GqlThrottlerGuard)
      .useValue({ canActivate: () => true })
      .compile();

    resolver = module.get(OAuthRevokeTokenResolver);
  };

  describe('Basic auth parsing', () => {
    it('decodes the Basic header into clientId + clientSecret', async () => {
      await buildModule();

      await resolver.oAuthRevokeToken(
        { token: 'opaque-refresh' },
        buildContext({ authorization: basic('aurora-cli', 'top-secret-12345') }),
      );

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ clientId: 'aurora-cli', clientSecret: 'top-secret-12345' }),
      );
    });

    it('forwards EMPTY client credentials when Authorization header is missing', async () => {
      await buildModule();

      await resolver.oAuthRevokeToken({ token: 'opaque-refresh' }, buildContext());

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ clientId: '', clientSecret: '' }),
      );
    });

    it('forwards EMPTY client credentials when Authorization scheme is not Basic', async () => {
      await buildModule();

      await resolver.oAuthRevokeToken(
        { token: 'opaque-refresh' },
        buildContext({ authorization: 'Bearer some-token' }),
      );

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ clientId: '', clientSecret: '' }),
      );
    });
  });

  describe('payload mapping', () => {
    it('forwards the trimmed token and token_type_hint', async () => {
      await buildModule();

      await resolver.oAuthRevokeToken(
        { token: '  opaque-refresh  ', tokenTypeHint: '  refresh_token  ' },
        buildContext({ authorization: basic('cli', 'sec') }),
      );

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ token: 'opaque-refresh', tokenTypeHint: 'refresh_token' }),
      );
    });
  });

  describe('response mapping', () => {
    it('returns the handler revoked flag as a Boolean', async () => {
      await buildModule();

      const result = await resolver.oAuthRevokeToken(
        { token: 'opaque-refresh' },
        buildContext({ authorization: basic('cli', 'sec') }),
      );

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

  describe('rate limiting', () => {
    it('guards the oAuthRevokeToken mutation with GqlThrottlerGuard', () => {
      const guards =
        (Reflect.getMetadata(
          '__guards__',
          OAuthRevokeTokenResolver.prototype.oAuthRevokeToken,
        ) as unknown[]) ?? [];

      expect(guards).toContain(GqlThrottlerGuard);
    });
  });
});
