import { PkceVerifierStoreService } from './pkce-verifier-store.service';

/**
 * Unit tests for PkceVerifierStoreService.
 *
 * PkceVerifierStoreService has no real NestJS dependencies — it is a plain injectable class
 * with an optional ttlMs primitive. We instantiate it directly to avoid NestJS DI trying to
 * inject the `number` type metadata (TypeScript reflection emits `Number` for the constructor
 * param, which NestJS cannot resolve from the DI container).
 *
 * Covers the invariants of the store:
 *   1. `take` returns the value exactly ONCE then nulls out (single-use).
 *   2. `take` of an unknown / never-stored state returns null.
 *   3. `take` of an already-consumed state returns null.
 *   4. Entries expire after the TTL elapses.
 */
describe('PkceVerifierStoreService', () => {
  let store: PkceVerifierStoreService;

  // Default TTL (5 min) — entries should not expire during the test run.
  beforeEach(() => {
    store = new PkceVerifierStoreService();
  });

  describe('put + take (happy path)', () => {
    it('returns the stored verifier on the first take', () => {
      store.put('state-abc', 'verifier-xyz');

      const result = store.take('state-abc');

      expect(result).toBe('verifier-xyz');
    });

    it('returns null on the SECOND take of the same state (single-use)', () => {
      store.put('state-abc', 'verifier-xyz');
      store.take('state-abc'); // first consume

      const result = store.take('state-abc');

      expect(result).toBeNull();
    });
  });

  describe('unknown state', () => {
    it('returns null for a state that was never stored', () => {
      const result = store.take('state-never-stored');

      expect(result).toBeNull();
    });
  });

  describe('already-consumed state', () => {
    it('returns null after the entry has been consumed', () => {
      store.put('state-one-time', 'verifier-one-time');
      store.take('state-one-time'); // consume

      expect(store.take('state-one-time')).toBeNull();
    });
  });

  describe('TTL expiry', () => {
    it('returns null for a state accessed after the TTL has elapsed', async () => {
      // 1 ms TTL to exercise expiry without long sleeps.
      const shortTtlStore = new PkceVerifierStoreService(1);

      shortTtlStore.put('state-expiring', 'verifier-expiring');

      // Wait long enough for the TTL to pass.
      await new Promise((resolve) => setTimeout(resolve, 10));

      const result = shortTtlStore.take('state-expiring');

      expect(result).toBeNull();
    });
  });

  describe('multiple independent states', () => {
    it('each state is independent — taking one does not affect others', () => {
      store.put('state-A', 'verifier-A');
      store.put('state-B', 'verifier-B');

      store.take('state-A'); // consume A

      expect(store.take('state-B')).toBe('verifier-B');
    });
  });
});
